@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,124 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { FileCode } from "lucide-react";
4
+ import { useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
5
+ import { useUiStrings } from "../i18n/provider.js";
6
+ import { cn } from "../lib/cn.js";
7
+ import { cachedTokens, getLoadedHighlighter, isLanguageReady, languageLabel, loadLanguage, plainLines, resolveLanguage, StreamingTokenizer, storeTokens, tokenizeCached, } from "../lib/shiki.js";
8
+ import { useCopyToClipboard } from "../lib/use-copy-to-clipboard.js";
9
+ import { CopyButton } from "./copy-button.js";
10
+ /** Its own module so a page can copy text without loading the highlighter. */
11
+ export { useCopyToClipboard };
12
+ /**
13
+ * Tokenized lines for `code`, without ever making a commit wait on Shiki:
14
+ *
15
+ * - **Instant** (rendered on the first pass): a {@link tokenCache} hit — e.g. a virtualized row
16
+ * scrolling back into view — or a stream that only grew (`streaming`: just the newly completed
17
+ * lines are tokenized; a finished stream seeds the cache with its result).
18
+ * - **Miss**: plain lines first, then a deferred (`useDeferredValue`, transition-priority and
19
+ * interruptible) re-render tokenizes and caches. Plain and highlighted output share the same line
20
+ * structure, so swapping them never changes layout or a virtualizer's measured height.
21
+ * - **Grammar not loaded yet**: plain lines until it is.
22
+ */
23
+ export function useHighlightedLines(code, lang, streaming = false) {
24
+ const [loadedLang, setLoadedLang] = useState(null);
25
+ const ready = lang === "text" || loadedLang === lang || isLanguageReady(lang);
26
+ const streamer = useRef(null);
27
+ useEffect(() => {
28
+ if (ready)
29
+ return undefined;
30
+ let cancelled = false;
31
+ const load = async () => {
32
+ try {
33
+ await loadLanguage(lang);
34
+ if (!cancelled)
35
+ setLoadedLang(lang);
36
+ }
37
+ catch {
38
+ // Unknown or failing grammar: stay on the plain-text rendering.
39
+ }
40
+ };
41
+ void load();
42
+ return () => {
43
+ cancelled = true;
44
+ };
45
+ }, [lang, ready]);
46
+ // What this render wants highlighted; its identity is the deferred value's identity.
47
+ const target = useMemo(() => (ready ? { code, lang, streaming } : null), [code, lang, ready, streaming]);
48
+ // Tokens that cost (almost) nothing now. The streamer lives in a ref as a per-instance memo: it
49
+ // only ever extends or resets itself, so a render React discards leaves it consistent.
50
+ const instant = useMemo(() => {
51
+ if (target === null)
52
+ return undefined;
53
+ const stream = streamer.current;
54
+ if (stream?.lang === target.lang && stream.isWarmFor(target.code)) {
55
+ const lines = stream.update(target.code);
56
+ if (!target.streaming) {
57
+ storeTokens(target.code, target.lang, lines);
58
+ streamer.current = null;
59
+ }
60
+ return { lines, highlighted: true };
61
+ }
62
+ if (target.streaming && target.lang !== "text")
63
+ return undefined;
64
+ const hit = cachedTokens(target.code, target.lang);
65
+ return hit === undefined ? undefined : { lines: hit, highlighted: true };
66
+ }, [target]);
67
+ const pending = instant === undefined ? target : null;
68
+ // On a miss the urgent render gets `null` (plain lines) and React schedules a deferred render that
69
+ // gets `pending`. No initial value on a hit, so cached blocks schedule no extra render at all.
70
+ const deferred = useDeferredValue(pending, pending === null ? undefined : null);
71
+ return useMemo(() => {
72
+ if (instant !== undefined)
73
+ return instant;
74
+ const h = getLoadedHighlighter();
75
+ if (target !== null && deferred === target && h !== null) {
76
+ if (!target.streaming)
77
+ return { lines: tokenizeCached(h, target.code, target.lang), highlighted: true };
78
+ if (streamer.current?.lang !== target.lang)
79
+ streamer.current = new StreamingTokenizer(h, target.lang);
80
+ return { lines: streamer.current.update(target.code), highlighted: true };
81
+ }
82
+ return { lines: plainLines(code), highlighted: false };
83
+ }, [instant, target, deferred, code]);
84
+ }
85
+ const PIN_THRESHOLD = 24;
86
+ export function CodeBlock({ code, lang, filename, showLineNumbers = false, startLine = 1, highlightLines, wrap = false, maxHeight, streaming = false, header = true, actions, className, ...props }) {
87
+ const source = code.endsWith("\n") ? code.slice(0, -1) : code;
88
+ const resolved = resolveLanguage(lang);
89
+ const { lines, highlighted } = useHighlightedLines(source, resolved, streaming);
90
+ const highlightSet = useMemo(() => new Set(highlightLines), [highlightLines]);
91
+ const label = languageLabel(lang);
92
+ const strings = useUiStrings().codeBlock;
93
+ // Stick to the bottom while streaming unless the reader scrolled up.
94
+ const scroller = useRef(null);
95
+ const pinned = useRef(true);
96
+ const onScroll = (event) => {
97
+ const el = event.currentTarget;
98
+ pinned.current = el.scrollHeight - el.scrollTop - el.clientHeight < PIN_THRESHOLD;
99
+ };
100
+ useLayoutEffect(() => {
101
+ const el = scroller.current;
102
+ if (streaming && pinned.current && el && lines.length > 0)
103
+ el.scrollTop = el.scrollHeight;
104
+ }, [streaming, lines]);
105
+ const gutter = {
106
+ "--code-gutter": `${String(startLine + Math.max(lines.length, 1) - 1).length}ch`,
107
+ };
108
+ const preStyle = maxHeight === undefined ? undefined : { maxHeight };
109
+ return (_jsxs("div", { "data-slot": "code-block", "data-language": resolved, className: cn("group/code relative isolate overflow-hidden rounded-xl border border-border-subtle bg-bg-inset text-fg", className), ...props, children: [header ? (_jsxs("div", { "data-slot": "code-block-header", className: "flex min-h-9 items-center gap-2 border-b border-border-subtle py-1 pe-1.5 ps-3.5", children: [filename ? (_jsxs(_Fragment, { children: [_jsx(FileCode, { "aria-hidden": true, className: "size-3.5 shrink-0 text-fg-subtle" }), _jsx("span", { className: "min-w-0 truncate font-mono text-xs text-fg-muted", children: filename })] })) : (_jsx("span", { className: "font-mono text-xs text-fg-subtle", children: label })), _jsxs("div", { className: "ms-auto flex min-w-0 items-center gap-0.5", children: [actions, _jsx(CopyButton, { value: source, label: strings.copyCode })] })] })) : (_jsx(CopyButton, { value: source, label: strings.copyCode, className: cn("absolute top-2 end-2 z-10 bg-bg-inset opacity-0 transition-opacity", "group-hover/code:opacity-100 focus-visible:opacity-100 motion-reduce:transition-none") })), _jsx("pre", { ref: scroller, dir: "ltr", "data-shiki": true, "data-highlighted": highlighted || undefined, "data-streaming": streaming || undefined,
110
+ // Scrollable regions must be reachable by keyboard (WCAG 2.1.1).
111
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: scroll container
112
+ tabIndex: 0, onScroll: maxHeight === undefined ? undefined : onScroll, style: preStyle, className: cn("overflow-auto overscroll-x-contain py-3 font-mono text-xs leading-5 outline-none", "focus-visible:ring-focus focus-visible:-outline-offset-2"), children: _jsx("code", { style: gutter, className: cn("grid min-w-full", wrap ? "w-full" : "w-max"), children: lines.map((line, index) => {
113
+ const n = startLine + index;
114
+ const emphasized = highlightSet.has(n);
115
+ return (_jsxs("span", { "data-line": n, "data-emphasized": emphasized || undefined, className: cn("flex min-h-5 px-4", emphasized && "bg-selected shadow-[inset_2px_0_0_var(--border-strong)]"), children: [showLineNumbers && (_jsx("span", { "aria-hidden": true, className: "me-4 w-(--code-gutter) shrink-0 select-none text-end tabular text-fg-subtle", children: n })), _jsx("span", { className: wrap ? "min-w-0 flex-1 whitespace-pre-wrap break-words" : "whitespace-pre", children: line.map((token, t) => (_jsx("span", { style: token.style, children: token.content }, t))) })] }, index));
116
+ }) }) })] }));
117
+ }
118
+ /* ------------------------------------------------------------------------------------------ */
119
+ /* Inline code */
120
+ /* ------------------------------------------------------------------------------------------ */
121
+ /** Inline code span for prose: hairline chip, mono, slightly smaller than surrounding text. */
122
+ export function Code({ className, ...props }) {
123
+ return (_jsx("code", { "data-slot": "code", className: cn("rounded-xs border border-border-subtle bg-bg-muted px-[0.35em] py-[0.08em]", "font-mono text-[0.86em] text-fg [overflow-wrap:anywhere] [box-decoration-break:clone]", className), ...props }));
124
+ }
@@ -0,0 +1,49 @@
1
+ import { useRender } from "@base-ui/react/use-render";
2
+ import { type VariantProps } from "../lib/cn.js";
3
+ import { type InputProps } from "./input.js";
4
+ export interface CodeInputProps extends Omit<InputProps, "inputMode" | "autoComplete" | "autoCapitalize" | "spellCheck" | "pattern" | "maxLength" | "type" | "size" | "inputClassName" | "dir"> {
5
+ /** How many characters the code has. */
6
+ readonly length: number;
7
+ /**
8
+ * Letters too, typed in capitals: a device code such as `ABCD-EFGH`, which the field doesn't check
9
+ * (the page reads it as it likes). Otherwise digits only, offered by one-time-code autofill.
10
+ */
11
+ readonly letters?: boolean;
12
+ }
13
+ /**
14
+ * The field for a code: a one-time code from an email (digits, `length` of them, offered by the
15
+ * platform's one-time-code autofill), or with `letters` a device code to type in, in large spaced
16
+ * figures. Put it in a `<Field>` for its label and error.
17
+ */
18
+ export declare function CodeInput({ length, letters, ...props }: CodeInputProps): import("react").JSX.Element;
19
+ declare const codeDisplay: import("tailwind-variants").TVReturnType<{
20
+ size: {
21
+ /** Inside a card or a settings flow. */
22
+ md: "rounded-lg px-4 py-3 text-md";
23
+ /** The page's subject: a device code to compare or type in. */
24
+ lg: "px-5 py-4 text-3xl";
25
+ };
26
+ }, undefined, string[], {
27
+ size: {
28
+ /** Inside a card or a settings flow. */
29
+ md: "rounded-lg px-4 py-3 text-md";
30
+ /** The page's subject: a device code to compare or type in. */
31
+ lg: "px-5 py-4 text-3xl";
32
+ };
33
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
34
+ size: {
35
+ /** Inside a card or a settings flow. */
36
+ md: "rounded-lg px-4 py-3 text-md";
37
+ /** The page's subject: a device code to compare or type in. */
38
+ lg: "px-5 py-4 text-3xl";
39
+ };
40
+ }, undefined>>;
41
+ export interface CodeDisplayProps extends useRender.ComponentProps<"code">, VariantProps<typeof codeDisplay> {
42
+ }
43
+ /**
44
+ * A code a person compares or types somewhere else (a device code, a check code), in spaced
45
+ * monospace figures. A `<code>` by default; `render={<button type="button" />}` when a click copies
46
+ * it. As a button its name is the code it shows; `title` says what the click does.
47
+ */
48
+ export declare function CodeDisplay({ size, className, render, ref, ...props }: CodeDisplayProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
49
+ export {};
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useRender } from "@base-ui/react/use-render";
4
+ import { cn, tv } from "../lib/cn.js";
5
+ import { cardStyles } from "./card.js";
6
+ import { Input } from "./input.js";
7
+ /**
8
+ * How every code reads, typed or shown: monospace figures with one letter-spacing, so each character
9
+ * stands apart. The indent matches the spacing the last character carries, so a centred code sits in
10
+ * the middle. A code is the same characters in every language, so it always runs left to right.
11
+ */
12
+ const CODE_TEXT = "text-center font-mono tabular tracking-[0.2em] indent-[0.2em]";
13
+ /**
14
+ * The field for a code: a one-time code from an email (digits, `length` of them, offered by the
15
+ * platform's one-time-code autofill), or with `letters` a device code to type in, in large spaced
16
+ * figures. Put it in a `<Field>` for its label and error.
17
+ */
18
+ export function CodeInput({ length, letters = false, ...props }) {
19
+ return (_jsx(Input, { size: "lg", inputMode: letters ? "text" : "numeric", autoComplete: letters ? "off" : "one-time-code", ...(letters ? { autoCapitalize: "characters", spellCheck: false } : { pattern: `[0-9]{${length}}` }), maxLength: length, dir: "ltr",
20
+ // Touch screens keep the large figures (the field's own rule sets 16 px there).
21
+ inputClassName: cn(CODE_TEXT, "text-xl pointer-coarse:text-xl", letters && "uppercase"), ...props }));
22
+ }
23
+ const codeDisplay = tv({
24
+ base: [
25
+ cardStyles({ size: "flush" }),
26
+ "block outline-none",
27
+ CODE_TEXT,
28
+ // Rendered as a button (a code that copies on click), it takes the hover wash and focus ring.
29
+ "transition-colors duration-(--duration-fast) enabled:hover:bg-hover focus-visible:ring-focus",
30
+ ],
31
+ variants: {
32
+ size: {
33
+ /** Inside a card or a settings flow. */
34
+ md: "rounded-lg px-4 py-3 text-md",
35
+ /** The page's subject: a device code to compare or type in. */
36
+ lg: "px-5 py-4 text-3xl",
37
+ },
38
+ },
39
+ defaultVariants: { size: "md" },
40
+ });
41
+ /**
42
+ * A code a person compares or types somewhere else (a device code, a check code), in spaced
43
+ * monospace figures. A `<code>` by default; `render={<button type="button" />}` when a click copies
44
+ * it. As a button its name is the code it shows; `title` says what the click does.
45
+ */
46
+ export function CodeDisplay({ size, className, render, ref, ...props }) {
47
+ return useRender({
48
+ defaultTagName: "code",
49
+ render,
50
+ ref,
51
+ props: {
52
+ "data-slot": "code-display",
53
+ dir: "ltr",
54
+ translate: "no",
55
+ className: cn(codeDisplay({ size }), className),
56
+ ...props,
57
+ },
58
+ });
59
+ }
@@ -0,0 +1,194 @@
1
+ import { Autocomplete as BaseAutocomplete } from "@base-ui/react/autocomplete";
2
+ import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
3
+ import { type ComponentProps, type ReactNode } from "react";
4
+ import { type VariantProps } from "../lib/cn.js";
5
+ declare const comboboxInputStyles: import("tailwind-variants").TVReturnType<{
6
+ size: {
7
+ sm: {
8
+ group: string;
9
+ input: string;
10
+ action: string;
11
+ };
12
+ md: {
13
+ group: string;
14
+ input: string;
15
+ action: string;
16
+ };
17
+ lg: {
18
+ group: string;
19
+ input: string;
20
+ action: string;
21
+ };
22
+ };
23
+ /** Chips mode: the frame grows to wrap selected values. */
24
+ chips: {
25
+ true: {
26
+ group: string;
27
+ };
28
+ };
29
+ }, {
30
+ group: string[];
31
+ input: string[];
32
+ action: string[];
33
+ adornment: string;
34
+ }, undefined, {
35
+ size: {
36
+ sm: {
37
+ group: string;
38
+ input: string;
39
+ action: string;
40
+ };
41
+ md: {
42
+ group: string;
43
+ input: string;
44
+ action: string;
45
+ };
46
+ lg: {
47
+ group: string;
48
+ input: string;
49
+ action: string;
50
+ };
51
+ };
52
+ /** Chips mode: the frame grows to wrap selected values. */
53
+ chips: {
54
+ true: {
55
+ group: string;
56
+ };
57
+ };
58
+ }, {
59
+ group: string[];
60
+ input: string[];
61
+ action: string[];
62
+ adornment: string;
63
+ }, import("tailwind-variants").TVReturnTypeLike<{
64
+ size: {
65
+ sm: {
66
+ group: string;
67
+ input: string;
68
+ action: string;
69
+ };
70
+ md: {
71
+ group: string;
72
+ input: string;
73
+ action: string;
74
+ };
75
+ lg: {
76
+ group: string;
77
+ input: string;
78
+ action: string;
79
+ };
80
+ };
81
+ /** Chips mode: the frame grows to wrap selected values. */
82
+ chips: {
83
+ true: {
84
+ group: string;
85
+ };
86
+ };
87
+ }, {
88
+ group: string[];
89
+ input: string[];
90
+ action: string[];
91
+ adornment: string;
92
+ }>>;
93
+ type ComboboxInputVariants = VariantProps<typeof comboboxInputStyles>;
94
+ export declare const ComboboxRoot: typeof BaseCombobox.Root;
95
+ /**
96
+ * Root for a field whose items act when picked (open a file, run a command) rather than become its
97
+ * value: the text stays free and nothing stays selected; `onValueChange` reports a pick with the
98
+ * reason `"item-press"`. Takes the same parts as `ComboboxRoot`. With `inline open` and no input,
99
+ * the list is the control: give `<ComboboxList>` `tabIndex={0}` and the arrows move its highlight,
100
+ * Enter picks.
101
+ */
102
+ export declare const AutocompleteRoot: typeof BaseAutocomplete.Root;
103
+ export interface ComboboxInputProps extends Omit<ComponentProps<typeof BaseCombobox.Input>, "size" | "className">, Omit<ComboboxInputVariants, "chips"> {
104
+ /** Icon before the text (e.g. a search glyph). */
105
+ leading?: ReactNode;
106
+ /** After the text, before the buttons (e.g. a spinner while results load). */
107
+ trailing?: ReactNode;
108
+ /** Show the ✕ button when a value is selected (in an autocomplete, when there's text). Default `true`. */
109
+ showClear?: boolean;
110
+ /** Accessible name of the ✕ button; default "Clear selection". A search field names what it clears. */
111
+ clearLabel?: string | undefined;
112
+ /** Show the ⌃⌄ popup button. Default `true`. */
113
+ showTrigger?: boolean;
114
+ /** Classes for the frame. */
115
+ className?: string | undefined;
116
+ inputClassName?: ComponentProps<typeof BaseCombobox.Input>["className"];
117
+ }
118
+ /** Text field that filters the list; includes clear and open buttons. */
119
+ export declare function ComboboxInput({ size, leading, trailing, showClear, clearLabel, showTrigger, className, inputClassName, ...props }: ComboboxInputProps): import("react").JSX.Element;
120
+ export interface ComboboxChipsInputProps<Value> extends Omit<ComponentProps<typeof BaseCombobox.Input>, "size" | "className" | "placeholder">, Omit<ComboboxInputVariants, "chips"> {
121
+ /** Text for each selected value's chip. */
122
+ getChipLabel: (value: Value) => ReactNode;
123
+ /** Shown only while nothing is selected. */
124
+ placeholder?: string | undefined;
125
+ className?: string | undefined;
126
+ }
127
+ /** Multiple-selection field: selected values render as removable chips before the caret. */
128
+ export declare function ComboboxChipsInput<Value>({ size, getChipLabel, placeholder, className, ...props }: ComboboxChipsInputProps<Value>): import("react").JSX.Element;
129
+ export interface ComboboxChipProps extends ComponentProps<typeof BaseCombobox.Chip> {
130
+ /** Accessible name for the remove button. */
131
+ removeLabel?: string;
132
+ }
133
+ export declare function ComboboxChip({ removeLabel, className, children, ...props }: ComboboxChipProps): import("react").JSX.Element;
134
+ export interface ComboboxContentProps extends ComponentProps<typeof BaseCombobox.Popup> {
135
+ side?: ComponentProps<typeof BaseCombobox.Positioner>["side"];
136
+ align?: ComponentProps<typeof BaseCombobox.Positioner>["align"];
137
+ sideOffset?: number;
138
+ }
139
+ /** Portal + positioner + popup. Children: <ComboboxEmpty> and <ComboboxList>. */
140
+ export declare function ComboboxContent({ side, align, sideOffset, className, ...props }: ComboboxContentProps): import("react").JSX.Element;
141
+ export declare function ComboboxList({ className, ...props }: ComponentProps<typeof BaseCombobox.List>): import("react").JSX.Element;
142
+ export declare function ComboboxEmpty({ className, ...props }: ComponentProps<typeof BaseCombobox.Empty>): import("react").JSX.Element;
143
+ export interface ComboboxItemProps extends Omit<ComponentProps<typeof BaseCombobox.Item>, "children"> {
144
+ icon?: ReactNode;
145
+ description?: ReactNode;
146
+ trailing?: ReactNode;
147
+ children?: ReactNode;
148
+ }
149
+ export declare function ComboboxItem({ icon, description, trailing, className, children, ...props }: ComboboxItemProps): import("react").JSX.Element;
150
+ export declare function ComboboxGroup({ className, ...props }: ComponentProps<typeof BaseCombobox.Group>): import("react").JSX.Element;
151
+ export declare function ComboboxGroupLabel({ className, ...props }: ComponentProps<typeof BaseCombobox.GroupLabel>): import("react").JSX.Element;
152
+ export declare function ComboboxSeparator({ className, ...props }: ComponentProps<typeof BaseCombobox.Separator>): import("react").JSX.Element;
153
+ export declare const ComboboxCollection: typeof BaseAutocomplete.Collection;
154
+ export interface ComboboxOption {
155
+ value: string;
156
+ /** Plain text — used for filtering and shown in the input/chips. */
157
+ label: string;
158
+ description?: ReactNode;
159
+ icon?: ReactNode;
160
+ disabled?: boolean;
161
+ /** Options sharing a `group` render under one heading (in first-seen order). */
162
+ group?: string;
163
+ }
164
+ interface ComboboxSharedProps extends Omit<ComboboxInputVariants, "chips">, Pick<BaseCombobox.Root.Props<string>, "disabled" | "readOnly" | "required" | "name" | "id" | "autoHighlight" | "open" | "onOpenChange"> {
165
+ items: readonly ComboboxOption[];
166
+ placeholder?: string | undefined;
167
+ /** Shown when the query matches nothing. */
168
+ emptyMessage?: ReactNode;
169
+ /** Icon before the text (single mode). */
170
+ leading?: ReactNode;
171
+ /** Required when there's no visible <Field> label. */
172
+ "aria-label"?: string;
173
+ /** Classes for the input frame. */
174
+ className?: string | undefined;
175
+ }
176
+ export interface ComboboxSingleProps extends ComboboxSharedProps {
177
+ multiple?: false;
178
+ value?: string | null;
179
+ defaultValue?: string | null;
180
+ onValueChange?: (value: string | null) => void;
181
+ }
182
+ export interface ComboboxMultipleProps extends ComboboxSharedProps {
183
+ multiple: true;
184
+ value?: readonly string[];
185
+ defaultValue?: readonly string[];
186
+ onValueChange?: (value: string[]) => void;
187
+ }
188
+ export type ComboboxProps = ComboboxSingleProps | ComboboxMultipleProps;
189
+ /**
190
+ * Filterable picker over string-valued options. `multiple` switches to chips.
191
+ * For custom item rendering or async data, compose the parts instead.
192
+ */
193
+ export declare function Combobox(props: ComboboxProps): import("react").JSX.Element;
194
+ export {};
@@ -0,0 +1,176 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Autocomplete as BaseAutocomplete } from "@base-ui/react/autocomplete";
4
+ import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
5
+ import { Check, ChevronsUpDown, X } from "lucide-react";
6
+ import { useMemo } from "react";
7
+ import { useUiStrings } from "../i18n/provider.js";
8
+ import { cn, resolveClassName, tv, withBase } from "../lib/cn.js";
9
+ import { usePortalContainer } from "../lib/theme-scope.js";
10
+ import { fieldChrome } from "./input.js";
11
+ import { ItemLabel } from "./item-label.js";
12
+ import { listboxGroupLabelClass, listboxIconClass, listboxIndicatorClass, listboxItemClass, listboxPopupClass, listboxSeparatorClass, } from "./select.js";
13
+ /* ------------------------------------------------------------------------------------------ */
14
+ /* Input */
15
+ /* ------------------------------------------------------------------------------------------ */
16
+ const comboboxInputStyles = tv({
17
+ slots: {
18
+ group: [
19
+ "group/combobox relative flex w-full min-w-0 cursor-text items-center",
20
+ fieldChrome,
21
+ "focus-within:border-border-strong focus-within:ring-3 data-popup-open:border-border-strong",
22
+ "has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-45",
23
+ "[&_svg]:shrink-0",
24
+ ],
25
+ input: [
26
+ "h-full min-w-0 flex-1 bg-transparent text-fg outline-none placeholder:text-fg-subtle",
27
+ "focus-visible:outline-none disabled:cursor-not-allowed",
28
+ ],
29
+ action: [
30
+ "flex shrink-0 items-center justify-center rounded-md text-fg-subtle outline-none",
31
+ "transition-[background-color,color,opacity,scale] duration-(--duration-fast)",
32
+ "hover:bg-hover hover:text-fg focus-visible:ring-focus",
33
+ "data-starting-style:scale-75 data-starting-style:opacity-0 data-ending-style:scale-75 data-ending-style:opacity-0",
34
+ ],
35
+ adornment: "flex shrink-0 items-center text-fg-subtle",
36
+ },
37
+ variants: {
38
+ size: {
39
+ sm: {
40
+ group: "h-7 gap-1 rounded-md pe-1 ps-2 text-sm [&_svg:not([class*='size-'])]:size-3.5",
41
+ input: "text-sm",
42
+ action: "size-5",
43
+ },
44
+ md: {
45
+ group: "h-8 gap-1.5 rounded-lg pe-1 ps-2.5 text-base [&_svg:not([class*='size-'])]:size-4",
46
+ input: "text-base",
47
+ action: "size-6",
48
+ },
49
+ lg: {
50
+ group: "h-10 gap-2 rounded-xl pe-1.5 ps-3 text-md [&_svg:not([class*='size-'])]:size-4",
51
+ input: "text-md",
52
+ action: "size-7",
53
+ },
54
+ },
55
+ /** Chips mode: the frame grows to wrap selected values. */
56
+ chips: {
57
+ true: { group: "h-auto flex-wrap py-1 ps-1" },
58
+ },
59
+ },
60
+ compoundVariants: [
61
+ { chips: true, size: "sm", class: { group: "min-h-7 py-0.5 ps-0.5" } },
62
+ { chips: true, size: "md", class: { group: "min-h-8" } },
63
+ { chips: true, size: "lg", class: { group: "min-h-10 py-1.5 ps-1.5" } },
64
+ ],
65
+ defaultVariants: { size: "md" },
66
+ });
67
+ export const ComboboxRoot = BaseCombobox.Root;
68
+ /**
69
+ * Root for a field whose items act when picked (open a file, run a command) rather than become its
70
+ * value: the text stays free and nothing stays selected; `onValueChange` reports a pick with the
71
+ * reason `"item-press"`. Takes the same parts as `ComboboxRoot`. With `inline open` and no input,
72
+ * the list is the control: give `<ComboboxList>` `tabIndex={0}` and the arrows move its highlight,
73
+ * Enter picks.
74
+ */
75
+ export const AutocompleteRoot = BaseAutocomplete.Root;
76
+ /** Text field that filters the list; includes clear and open buttons. */
77
+ export function ComboboxInput({ size, leading, trailing, showClear = true, clearLabel, showTrigger = true, className, inputClassName, ...props }) {
78
+ const styles = comboboxInputStyles({ size });
79
+ const t = useUiStrings().combobox;
80
+ return (_jsxs(BaseCombobox.InputGroup, { "data-slot": "combobox-input", className: styles.group({ className }), children: [leading !== undefined && (_jsx("span", { "aria-hidden": true, className: styles.adornment(), children: leading })), _jsx(BaseCombobox.Input, { className: (state) => styles.input({
81
+ className: resolveClassName(inputClassName, state),
82
+ }), ...props }), trailing !== undefined && _jsx("span", { className: styles.adornment(), children: trailing }), showClear && (_jsx(BaseCombobox.Clear, { "aria-label": clearLabel ?? t.clearSelection, className: styles.action(), children: _jsx(X, { "aria-hidden": true, className: "size-3.5" }) })), showTrigger && (_jsx(BaseCombobox.Trigger, { "aria-label": t.showOptions, className: styles.action(), children: _jsx(ChevronsUpDown, { "aria-hidden": true, className: "size-3.5" }) }))] }));
83
+ }
84
+ /** Multiple-selection field: selected values render as removable chips before the caret. */
85
+ export function ComboboxChipsInput({ size, getChipLabel, placeholder, className, ...props }) {
86
+ const styles = comboboxInputStyles({ size, chips: true });
87
+ const chipSize = size === "sm" ? "h-5 text-xs" : "h-6 text-sm";
88
+ const t = useUiStrings().combobox;
89
+ return (_jsx(BaseCombobox.InputGroup, { "data-slot": "combobox-chips-input", className: styles.group({ className }), children: _jsx(BaseCombobox.Chips, { className: "flex min-w-0 flex-1 flex-wrap items-center gap-1", children: _jsx(BaseCombobox.Value, { children: (selected) => (_jsxs(_Fragment, { children: [selected.map((value) => {
90
+ const label = getChipLabel(value);
91
+ return (_jsx(ComboboxChip, { className: chipSize, removeLabel: typeof label === "string" ? t.removeItem({ label }) : t.remove, children: label }, String(value)));
92
+ }), _jsx(BaseCombobox.Input, { placeholder: selected.length > 0 ? undefined : placeholder, className: cn(styles.input(), "h-6 min-w-16 px-1.5"), ...props })] })) }) }) }));
93
+ }
94
+ export function ComboboxChip({ removeLabel, className, children, ...props }) {
95
+ const t = useUiStrings().combobox;
96
+ return (_jsxs(BaseCombobox.Chip, { "data-slot": "combobox-chip", className: withBase(cn("inline-flex max-w-full cursor-default items-center gap-0.5 rounded-md bg-active pe-0.5 ps-1.5 font-medium text-fg outline-none", "transition-[background-color,color] duration-(--duration-instant)", "focus-within:bg-primary focus-within:text-primary-fg data-highlighted:bg-primary data-highlighted:text-primary-fg"), className), ...props, children: [_jsx("span", { className: "min-w-0 truncate", children: children }), _jsx(BaseCombobox.ChipRemove, { "aria-label": removeLabel ?? t.remove, className: "flex size-4 items-center justify-center rounded-sm text-current opacity-60 outline-none transition-opacity hover:opacity-100", children: _jsx(X, { "aria-hidden": true, className: "size-3" }) })] }));
97
+ }
98
+ /** Portal + positioner + popup. Children: <ComboboxEmpty> and <ComboboxList>. */
99
+ export function ComboboxContent({ side = "bottom", align = "start", sideOffset = 6, className, ...props }) {
100
+ const container = usePortalContainer();
101
+ return (_jsx(BaseCombobox.Portal, { container: container, children: _jsx(BaseCombobox.Positioner, { side: side, align: align, sideOffset: sideOffset, collisionPadding: 8, className: "z-(--z-overlay) outline-none", children: _jsx(BaseCombobox.Popup, { "data-slot": "combobox-content", className: withBase(cn(listboxPopupClass, "w-(--anchor-width)"), className), ...props }) }) }));
102
+ }
103
+ export function ComboboxList({ className, ...props }) {
104
+ return (_jsx(BaseCombobox.List, { "data-slot": "combobox-list", className: withBase(
105
+ // `focus-visible`: an inline list without a field takes focus itself.
106
+ "max-h-[min(22rem,var(--available-height))] scroll-py-1 overflow-y-auto overscroll-contain outline-none focus-visible:ring-focus scrollbar-none data-empty:hidden", className), ...props }));
107
+ }
108
+ export function ComboboxEmpty({ className, ...props }) {
109
+ return (_jsx(BaseCombobox.Empty, { "data-slot": "combobox-empty", className: withBase("px-2 py-5 text-center text-sm text-fg-subtle empty:hidden", className), ...props }));
110
+ }
111
+ export function ComboboxItem({ icon, description, trailing, className, children, ...props }) {
112
+ return (_jsxs(BaseCombobox.Item, { "data-slot": "combobox-item", className: withBase(listboxItemClass, className), ...props, children: [icon !== undefined && (_jsx("span", { "aria-hidden": true, className: listboxIconClass, children: icon })), _jsx(ItemLabel, { description: description, children: children }), trailing, _jsx(BaseCombobox.ItemIndicator, { className: listboxIndicatorClass, children: _jsx(Check, { "aria-hidden": true, strokeWidth: 2.25, className: "size-3.5" }) })] }));
113
+ }
114
+ export function ComboboxGroup({ className, ...props }) {
115
+ return (_jsx(BaseCombobox.Group, { "data-slot": "combobox-group", className: withBase("flex flex-col", className), ...props }));
116
+ }
117
+ export function ComboboxGroupLabel({ className, ...props }) {
118
+ return (_jsx(BaseCombobox.GroupLabel, { "data-slot": "combobox-group-label", className: withBase(listboxGroupLabelClass, className), ...props }));
119
+ }
120
+ export function ComboboxSeparator({ className, ...props }) {
121
+ return (_jsx(BaseCombobox.Separator, { "data-slot": "combobox-separator", className: withBase(listboxSeparatorClass, className), ...props }));
122
+ }
123
+ export const ComboboxCollection = BaseCombobox.Collection;
124
+ const optionValue = (option) => option.value;
125
+ const optionLabel = (option) => option.label;
126
+ function groupOptions(items) {
127
+ const groups = new Map();
128
+ for (const option of items) {
129
+ const key = option.group ?? "";
130
+ const bucket = groups.get(key);
131
+ if (bucket)
132
+ bucket.push(option);
133
+ else
134
+ groups.set(key, [option]);
135
+ }
136
+ return [...groups].map(([label, options]) => ({ label, items: options }));
137
+ }
138
+ function renderOption(option) {
139
+ return (_jsx(ComboboxItem, { value: option.value, icon: option.icon, description: option.description, disabled: option.disabled === true, children: option.label }, option.value));
140
+ }
141
+ /**
142
+ * Filterable picker over string-valued options. `multiple` switches to chips.
143
+ * For custom item rendering or async data, compose the parts instead.
144
+ */
145
+ export function Combobox(props) {
146
+ const { items, placeholder, emptyMessage, leading, size, className, "aria-label": ariaLabel, disabled, readOnly, required, name, id, autoHighlight = true, open, onOpenChange, } = props;
147
+ const t = useUiStrings().combobox;
148
+ const grouped = items.some((option) => option.group !== undefined);
149
+ const collection = useMemo(() => grouped
150
+ ? BaseCombobox.createItems(groupOptions(items), { getValue: optionValue, getLabel: optionLabel })
151
+ : BaseCombobox.createItems(items, { getValue: optionValue, getLabel: optionLabel }), [items, grouped]);
152
+ const labels = useMemo(() => new Map(items.map((option) => [option.value, option.label])), [items]);
153
+ const rootProps = {
154
+ items: collection,
155
+ autoHighlight,
156
+ ...(disabled === undefined ? {} : { disabled }),
157
+ ...(readOnly === undefined ? {} : { readOnly }),
158
+ ...(required === undefined ? {} : { required }),
159
+ ...(name === undefined ? {} : { name }),
160
+ ...(id === undefined ? {} : { id }),
161
+ ...(open === undefined ? {} : { open }),
162
+ ...(onOpenChange === undefined ? {} : { onOpenChange }),
163
+ };
164
+ const labelProps = ariaLabel === undefined ? {} : { "aria-label": ariaLabel };
165
+ const content = (_jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: emptyMessage ?? t.noMatches }), _jsx(ComboboxList, { children: grouped
166
+ ? (group, index) => (_jsxs(ComboboxGroup, { items: group.items, children: [index > 0 && _jsx(ComboboxSeparator, {}), group.label !== "" && _jsx(ComboboxGroupLabel, { children: group.label }), _jsx(ComboboxCollection, { children: (option) => renderOption(option) })] }, group.label || "_"))
167
+ : (option) => renderOption(option) })] }));
168
+ if (props.multiple === true) {
169
+ const { value, defaultValue, onValueChange } = props;
170
+ return (_jsxs(BaseCombobox.Root, { multiple: true, ...rootProps, ...(value === undefined ? {} : { value }), ...(defaultValue === undefined ? {} : { defaultValue }), ...(onValueChange === undefined ? {} : { onValueChange: (next) => onValueChange(next) }), children: [_jsx(ComboboxChipsInput, { size: size, className: className, getChipLabel: (chip) => labels.get(chip) ?? chip, ...(placeholder === undefined ? {} : { placeholder }), ...labelProps }), content] }));
171
+ }
172
+ const { value, defaultValue, onValueChange } = props;
173
+ return (_jsxs(BaseCombobox.Root, { ...rootProps, ...(value === undefined ? {} : { value }), ...(defaultValue === undefined ? {} : { defaultValue }), ...(onValueChange === undefined
174
+ ? {}
175
+ : { onValueChange: (next) => onValueChange(next) }), children: [_jsx(ComboboxInput, { size: size, leading: leading, className: className, ...(placeholder === undefined ? {} : { placeholder }), ...labelProps }), content] }));
176
+ }