@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,109 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Input as BaseInput } from "@base-ui/react/input";
4
+ import { Search, X } from "lucide-react";
5
+ import { useUiStrings } from "../i18n/provider.js";
6
+ import { cn, resolveClassName, tv } from "../lib/cn.js";
7
+ import { isComposing } from "../lib/keys.js";
8
+ import { IconButton } from "./button.js";
9
+ /**
10
+ * The shared "field chrome" for every text-entry surface (Input, Textarea, Select trigger,
11
+ * Combobox): quiet surface, hairline border, firmer border on hover. Each consumer adds its own
12
+ * focus selector with `border-border-strong ring-3` — the halo colour (focus blue, or danger when
13
+ * invalid) is preset here. Invalid follows Base UI's `data-invalid` (inside <Field>) or
14
+ * `aria-invalid`.
15
+ */
16
+ export const fieldChrome = cn("border border-border bg-surface text-fg shadow-xs ring-focus/35", "transition-[border-color,box-shadow,background-color,opacity] duration-(--duration-fast) ease-(--ease-standard)", "hover:border-border-strong", "data-invalid:border-danger-border! data-invalid:ring-danger/20", "aria-invalid:border-danger-border! aria-invalid:ring-danger/20", "has-[[data-invalid]]:border-danger-border! has-[[data-invalid]]:ring-danger/20", "has-[[aria-invalid=true]]:border-danger-border! has-[[aria-invalid=true]]:ring-danger/20");
17
+ const inputStyles = tv({
18
+ slots: {
19
+ root: [
20
+ "group/input relative flex w-full min-w-0 cursor-text items-center touch-target",
21
+ fieldChrome,
22
+ "has-[input:focus]:border-border-strong has-[input:focus]:ring-3",
23
+ "has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-45",
24
+ "has-[input:read-only]:bg-bg-subtle has-[input:read-only]:shadow-none has-[input:read-only]:hover:border-border",
25
+ "[&_svg]:shrink-0",
26
+ ],
27
+ input: [
28
+ "h-full min-w-0 flex-1 bg-transparent text-fg outline-none placeholder:text-fg-subtle",
29
+ // iOS zooms into a field whose text is under 16 px; touch screens get 16 px.
30
+ "pointer-coarse:text-[1rem]",
31
+ "focus-visible:outline-none disabled:cursor-not-allowed",
32
+ "[&::-webkit-search-cancel-button]:appearance-none",
33
+ "file:me-2 file:border-0 file:bg-transparent file:font-medium file:text-fg",
34
+ ],
35
+ adornment: "pointer-events-auto flex shrink-0 items-center text-fg-subtle",
36
+ },
37
+ variants: {
38
+ size: {
39
+ sm: {
40
+ root: "h-7 gap-1.5 rounded-md px-2 text-sm [&_svg:not([class*='size-'])]:size-3.5",
41
+ input: "text-sm",
42
+ },
43
+ md: {
44
+ root: "h-8 gap-2 rounded-lg px-2.5 text-base [&_svg:not([class*='size-'])]:size-4",
45
+ input: "text-base",
46
+ },
47
+ lg: {
48
+ root: "h-10 gap-2.5 rounded-xl px-3 text-md [&_svg:not([class*='size-'])]:size-4",
49
+ input: "text-md",
50
+ },
51
+ },
52
+ /**
53
+ * `wash`: a resting field on chrome (a sidebar's search): ink over whatever is beneath and no
54
+ * border until it has focus, when it takes the field's surface. Paper's own surface reads yellow
55
+ * over light vibrancy.
56
+ */
57
+ appearance: {
58
+ field: {},
59
+ wash: {
60
+ root: "border-transparent bg-field-wash shadow-none hover:border-transparent has-[input:focus]:border-border has-[input:focus]:bg-surface",
61
+ },
62
+ /**
63
+ * `bare`: text you edit in place, inside a heading or a card (a task's title): no chrome at rest
64
+ * and the surrounding type (size, weight, line height), a wash on hover and focus, and the focus
65
+ * halo. Touch screens still get at least 16 px, so iOS doesn't zoom.
66
+ */
67
+ bare: {
68
+ root: [
69
+ "-mx-1 h-auto rounded-md border-transparent bg-transparent px-1 text-[length:inherit] shadow-none",
70
+ "hover:border-transparent hover:bg-hover",
71
+ "has-[input:focus]:border-transparent has-[input:focus]:bg-hover has-[input:focus]:ring-2",
72
+ ],
73
+ input: "text-[length:inherit] pointer-coarse:text-[max(1rem,1em)]",
74
+ },
75
+ },
76
+ },
77
+ defaultVariants: { size: "md", appearance: "field" },
78
+ });
79
+ /** Clicking the frame's padding or a decorative adornment focuses the input, like a native field. */
80
+ function focusInputOnFramePress(event) {
81
+ const target = event.target;
82
+ if (!(target instanceof Element))
83
+ return;
84
+ if (target.closest("input, button, a, [role='button'], [tabindex]"))
85
+ return;
86
+ const input = event.currentTarget.querySelector("input");
87
+ if (!input || input.disabled)
88
+ return;
89
+ event.preventDefault();
90
+ input.focus();
91
+ }
92
+ export function Input({ size, appearance, leading, trailing, invalid, className, inputClassName, ...props }) {
93
+ const styles = inputStyles({ size, appearance });
94
+ return (_jsxs("div", { "data-slot": "input", className: styles.root({ className }), onPointerDown: focusInputOnFramePress, children: [leading !== undefined && (_jsx("span", { "data-slot": "input-leading", className: styles.adornment(), children: leading })), _jsx(BaseInput, { "data-slot": "input-control", "aria-invalid": invalid === true ? true : undefined, className: (state) => styles.input({
95
+ className: resolveClassName(inputClassName, state),
96
+ }), ...props }), trailing !== undefined && (_jsx("span", { "data-slot": "input-trailing", className: styles.adornment(), children: trailing }))] }));
97
+ }
98
+ /** A search field: the search glyph before the text, no autocomplete or spellcheck. */
99
+ export function SearchInput({ label, placeholder, onClear, trailing, onKeyDown, ...props }) {
100
+ const t = useUiStrings().primitives;
101
+ const clearable = onClear !== undefined && props.value !== undefined && String(props.value) !== "";
102
+ return (_jsx(Input, { type: "search", "aria-label": label, placeholder: placeholder ?? label, leading: _jsx(Search, { "aria-hidden": true }), trailing: clearable ? (_jsx(IconButton, { "aria-label": t.clearSearch, size: "xs", onClick: onClear, children: _jsx(X, {}) })) : (trailing), autoComplete: "off", spellCheck: false, onKeyDown: (event) => {
103
+ onKeyDown?.(event);
104
+ if (clearable && event.key === "Escape" && !event.defaultPrevented && !isComposing(event)) {
105
+ event.preventDefault();
106
+ onClear();
107
+ }
108
+ }, ...props }));
109
+ }
@@ -0,0 +1,11 @@
1
+ import type { ElementType, ReactNode } from "react";
2
+ /** A menu, select or combobox item's label, with its description under it when there is one. */
3
+ export declare function ItemLabel({ description, as: Text, children, }: {
4
+ readonly description?: ReactNode;
5
+ /** What renders the label itself: a span, or a select's `ItemText`, which its trigger shows once picked. */
6
+ readonly as?: ElementType<{
7
+ className?: string;
8
+ children?: ReactNode;
9
+ }>;
10
+ readonly children?: ReactNode;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** A menu, select or combobox item's label, with its description under it when there is one. */
3
+ export function ItemLabel({ description, as: Text = "span", children, }) {
4
+ return description === undefined ? (_jsx(Text, { className: "min-w-0 flex-1 truncate", children: children })) : (_jsxs("span", { className: "flex min-w-0 flex-1 flex-col py-1.5", children: [_jsx(Text, { className: "truncate leading-5", children: children }), _jsx("span", { className: "line-clamp-2 text-pretty text-xs leading-4 text-fg-subtle", children: description })] }));
5
+ }
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ export interface ItemRowProps extends Omit<ComponentProps<"div">, "title"> {
3
+ /** Leading glyph in a tile; an icon in it is sized and muted for you. */
4
+ icon?: ReactNode;
5
+ /** The item's name, with any badge after it. Wrap a long name in a `truncate` span. */
6
+ title: ReactNode;
7
+ /** One muted line under the name: cut with an ellipsis when it doesn't fit, whole in a tooltip. */
8
+ detail?: ReactNode;
9
+ /** Its controls. On a narrow card they drop under the text rather than squeeze it. */
10
+ trailing?: ReactNode;
11
+ /** More under the detail, wrapping: a status note, a reason. */
12
+ children?: ReactNode;
13
+ }
14
+ /**
15
+ * One item in a flush, divided card (`Card size="flush" divided`): an icon tile, its name and a muted
16
+ * detail, its controls at the end. A device, a key, a member, an invoice. `CardRow` is the padding
17
+ * alone, for a row that lays out its own content.
18
+ */
19
+ export declare function ItemRow({ icon, title, detail, trailing, children, className, ...props }: ItemRowProps): import("react").JSX.Element;
20
+ /** `ItemRow`s while a list loads: the tile, a name and a detail line, `rows` times. */
21
+ export declare function ItemRowSkeleton({ rows }: {
22
+ readonly rows?: number;
23
+ }): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ import { Skeleton } from "./skeleton.js";
5
+ import { TruncatedText } from "./truncated-text.js";
6
+ const tile = "flex size-8 shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg [&_svg]:size-4 [&_svg]:text-fg-subtle";
7
+ const frame = "flex min-h-14 items-center gap-x-3 gap-y-2 px-4 py-2.5";
8
+ /**
9
+ * One item in a flush, divided card (`Card size="flush" divided`): an icon tile, its name and a muted
10
+ * detail, its controls at the end. A device, a key, a member, an invoice. `CardRow` is the padding
11
+ * alone, for a row that lays out its own content.
12
+ */
13
+ export function ItemRow({ icon, title, detail, trailing, children, className, ...props }) {
14
+ return (_jsxs("div", { "data-slot": "item-row", className: cn(frame, "flex-wrap", className), ...props, children: [_jsxs("div", { className: cn("flex min-w-0 grow items-center gap-x-3", icon === undefined ? "basis-48" : "basis-60"), children: [icon !== undefined && _jsx("span", { className: tile, children: icon }), _jsxs("div", { className: "flex min-w-0 grow flex-col", children: [_jsx("span", { className: "flex min-w-0 items-center gap-2 text-base text-fg", children: title }), detail !== undefined && _jsx(TruncatedText, { className: "text-sm text-fg-subtle", children: detail }), children !== undefined && _jsx("div", { className: "text-sm text-fg-subtle", children: children })] })] }), trailing !== undefined && _jsx("div", { className: "ms-auto flex shrink-0 items-center gap-1.5", children: trailing })] }));
15
+ }
16
+ /** `ItemRow`s while a list loads: the tile, a name and a detail line, `rows` times. */
17
+ export function ItemRowSkeleton({ rows = 1 }) {
18
+ return (_jsx(_Fragment, { children: Array.from({ length: rows }, (_, i) => i).map((row) => (_jsxs("div", { "data-slot": "item-row-skeleton", "aria-hidden": true, className: frame, children: [_jsx(Skeleton, { className: "size-8 rounded-lg" }), _jsxs("div", { className: "flex flex-1 flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3.5 w-32" }), _jsx(Skeleton, { className: "h-3 w-48 max-w-full" })] })] }, row))) }));
19
+ }
@@ -0,0 +1,55 @@
1
+ import type { ComponentProps } from "react";
2
+ import { type UiStrings } from "../i18n/strings/index.js";
3
+ import { type VariantProps } from "../lib/cn.js";
4
+ declare const kbd: import("tailwind-variants").TVReturnType<{
5
+ size: {
6
+ sm: "h-4 min-w-4 px-1 text-xs leading-none";
7
+ md: "h-5 min-w-5 px-1.5 text-xs leading-none";
8
+ };
9
+ /** `bare` drops the keycap chrome — for hints inside menus and buttons. */
10
+ bare: {
11
+ true: "border-transparent bg-transparent px-0 text-fg-subtle shadow-none [box-shadow:none]";
12
+ };
13
+ }, undefined, string[], {
14
+ size: {
15
+ sm: "h-4 min-w-4 px-1 text-xs leading-none";
16
+ md: "h-5 min-w-5 px-1.5 text-xs leading-none";
17
+ };
18
+ /** `bare` drops the keycap chrome — for hints inside menus and buttons. */
19
+ bare: {
20
+ true: "border-transparent bg-transparent px-0 text-fg-subtle shadow-none [box-shadow:none]";
21
+ };
22
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
23
+ size: {
24
+ sm: "h-4 min-w-4 px-1 text-xs leading-none";
25
+ md: "h-5 min-w-5 px-1.5 text-xs leading-none";
26
+ };
27
+ /** `bare` drops the keycap chrome — for hints inside menus and buttons. */
28
+ bare: {
29
+ true: "border-transparent bg-transparent px-0 text-fg-subtle shadow-none [box-shadow:none]";
30
+ };
31
+ }, undefined>>;
32
+ type KbdStrings = UiStrings["kbd"];
33
+ export interface KbdProps extends ComponentProps<"kbd">, VariantProps<typeof kbd> {
34
+ }
35
+ /** A single keycap. */
36
+ export declare function Kbd({ size, bare, className, ...props }: KbdProps): import("react").JSX.Element;
37
+ /** A chord in words for screen readers: `["mod", "k"]` → "Command K" on Apple, "Control K" elsewhere. */
38
+ export declare function spokenShortcut(keys: readonly string[], mac?: boolean, strings?: KbdStrings): string;
39
+ export interface ShortcutProps extends Omit<ComponentProps<"span">, "children">, VariantProps<typeof kbd> {
40
+ /** Logical keys, e.g. `["mod", "k"]` → ⌘K on macOS, Ctrl K elsewhere. */
41
+ keys: readonly string[];
42
+ /**
43
+ * Announce the chord in words ("Command K"). Off by default: a shortcut inside a button, menu
44
+ * item or tooltip is announced through the control's `aria-keyshortcuts` (see
45
+ * `ariaKeyShortcuts`), so its name doesn't double up as "New document⌘N⌘N". Turn on for
46
+ * standalone hints and shortcut lists, where the keys are the content.
47
+ */
48
+ spoken?: boolean;
49
+ }
50
+ /**
51
+ * A key combination rendered as keycaps with platform-aware glyphs. The keycaps are always
52
+ * `aria-hidden`; at most one spoken form exists (the `spoken` words), never glyphs plus words.
53
+ */
54
+ export declare function Shortcut({ keys, size, bare, spoken, className, ...props }: ShortcutProps): import("react").JSX.Element;
55
+ export {};
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiStrings } from "../i18n/provider.js";
4
+ import { EN_UI_STRINGS } from "../i18n/strings/index.js";
5
+ import { cn, tv } from "../lib/cn.js";
6
+ import { keyGlyph } from "../lib/keycaps.js";
7
+ import { keyed } from "../lib/keyed.js";
8
+ import { isMacPlatform } from "../lib/keys.js";
9
+ const kbd = tv({
10
+ base: [
11
+ "inline-flex select-none items-center justify-center gap-0.5 rounded-xs font-sans font-medium tabular",
12
+ "border border-border bg-surface text-fg-muted [box-shadow:inset_0_-1px_0_0_var(--border)]",
13
+ ],
14
+ variants: {
15
+ size: {
16
+ sm: "h-4 min-w-4 px-1 text-xs leading-none",
17
+ md: "h-5 min-w-5 px-1.5 text-xs leading-none",
18
+ },
19
+ /** `bare` drops the keycap chrome — for hints inside menus and buttons. */
20
+ bare: { true: "border-transparent bg-transparent px-0 text-fg-subtle shadow-none [box-shadow:none]" },
21
+ },
22
+ defaultVariants: { size: "md" },
23
+ });
24
+ /** A single keycap. */
25
+ export function Kbd({ size, bare, className, ...props }) {
26
+ return _jsx("kbd", { "data-slot": "kbd", className: cn(kbd({ size, bare }), className), ...props });
27
+ }
28
+ /** Spoken names per logical key: [Apple, elsewhere]. */
29
+ function spokenNames(spoken) {
30
+ return {
31
+ mod: [spoken.command, spoken.control],
32
+ cmd: [spoken.command, spoken.control],
33
+ ctrl: [spoken.control, spoken.control],
34
+ alt: [spoken.option, spoken.alt],
35
+ option: [spoken.option, spoken.alt],
36
+ shift: [spoken.shift, spoken.shift],
37
+ enter: [spoken.return, spoken.enter],
38
+ return: [spoken.return, spoken.enter],
39
+ esc: [spoken.escape, spoken.escape],
40
+ escape: [spoken.escape, spoken.escape],
41
+ tab: [spoken.tab, spoken.tab],
42
+ space: [spoken.space, spoken.space],
43
+ up: [spoken.upArrow, spoken.upArrow],
44
+ down: [spoken.downArrow, spoken.downArrow],
45
+ left: [spoken.leftArrow, spoken.leftArrow],
46
+ right: [spoken.rightArrow, spoken.rightArrow],
47
+ backspace: [spoken.delete, spoken.backspace],
48
+ delete: [spoken.forwardDelete, spoken.delete],
49
+ };
50
+ }
51
+ /** A chord in words for screen readers: `["mod", "k"]` → "Command K" on Apple, "Control K" elsewhere. */
52
+ export function spokenShortcut(keys, mac = isMacPlatform(), strings = EN_UI_STRINGS.kbd) {
53
+ const names = spokenNames(strings.spoken);
54
+ const word = (key) => {
55
+ const spoken = names[key.toLowerCase()];
56
+ if (spoken)
57
+ return mac ? spoken[0] : spoken[1];
58
+ return keyGlyph(key, mac, strings);
59
+ };
60
+ return runsOf(keys)
61
+ .map(({ key, count }) => (count > 1 ? strings.spoken.repeated({ key: word(key), count }) : word(key)))
62
+ .join(" ");
63
+ }
64
+ /** Consecutive presses of one key fold into a run: `["esc", "esc"]` → one key pressed twice. */
65
+ function runsOf(keys) {
66
+ const runs = [];
67
+ for (const key of keys) {
68
+ const last = runs.at(-1);
69
+ if (last !== undefined && last.key.toLowerCase() === key.toLowerCase())
70
+ last.count += 1;
71
+ else
72
+ runs.push({ key, count: 1 });
73
+ }
74
+ return runs;
75
+ }
76
+ /**
77
+ * A key combination rendered as keycaps with platform-aware glyphs. The keycaps are always
78
+ * `aria-hidden`; at most one spoken form exists (the `spoken` words), never glyphs plus words.
79
+ */
80
+ export function Shortcut({ keys, size, bare, spoken = false, className, ...props }) {
81
+ const mac = isMacPlatform();
82
+ const strings = useUiStrings().kbd;
83
+ // A double press ("Esc Esc") reads as one keycap pressed twice, not two caps jammed together.
84
+ const caps = keyed(runsOf(keys), (run) => run.key).map(({ item: { key, count }, key: id }) => (_jsxs("span", { className: "inline-flex items-center gap-0.5", children: [_jsx(Kbd, { size: size, bare: bare, "aria-hidden": true, children: keyGlyph(key, mac, strings) }), count > 1 && (_jsxs("span", { "aria-hidden": true, className: "text-xs leading-none text-fg-subtle tabular", children: ["\u00D7", count] }))] }, id)));
85
+ return (_jsxs("span", { "data-slot": "shortcut", className: cn("inline-flex items-center gap-0.5", className), ...(spoken ? {} : { "aria-hidden": true }), ...props, children: [spoken && _jsx("span", { className: "sr-only", children: spokenShortcut(keys, mac, strings) }), caps] }));
86
+ }
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * How a message's links to other sites open on the surface it renders on: `open` at once; `check`
4
+ * first shows the whole address when what the link says isn't that address (Cloud Chat, cloud-chat.md
5
+ * §8), so a link built from the conversation's text can't leave unseen.
6
+ */
7
+ export type LinkPolicy = "open" | "check";
8
+ export declare function LinkPolicyProvider({ value, children, }: {
9
+ readonly value: LinkPolicy;
10
+ readonly children: ReactNode;
11
+ }): import("react").JSX.Element;
12
+ export declare function useLinkPolicy(): LinkPolicy;
13
+ /** The link says its own address (an autolink, or a URL pasted as its text): nothing hides behind it. */
14
+ export declare function linkShowsAddress(text: string, href: string): boolean;
15
+ /**
16
+ * An external address waiting for the person: the whole of it, then Open (as the link would have,
17
+ * in a new tab with no opener), Copy or Cancel. `trigger` is what they clicked. The dialog loads where a
18
+ * check renders, so a surface that only opens links doesn't carry it; a click while it loads opens it once
19
+ * it has, and the link never leaves unseen.
20
+ */
21
+ export declare function ExternalLinkCheck({ href, trigger, }: {
22
+ readonly href: string;
23
+ readonly trigger: (open: () => void) => ReactNode;
24
+ }): import("react").JSX.Element;
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, lazy, Suspense, use, useState } from "react";
4
+ const LinkPolicyContext = createContext("open");
5
+ export function LinkPolicyProvider({ value, children, }) {
6
+ return _jsx(LinkPolicyContext, { value: value, children: children });
7
+ }
8
+ export function useLinkPolicy() {
9
+ return use(LinkPolicyContext);
10
+ }
11
+ /** An address as a person reads it: without its scheme or a closing slash. */
12
+ function bare(text) {
13
+ return text
14
+ .trim()
15
+ .replace(/^https?:\/\//iu, "")
16
+ .replace(/\/$/u, "");
17
+ }
18
+ /** The link says its own address (an autolink, or a URL pasted as its text): nothing hides behind it. */
19
+ export function linkShowsAddress(text, href) {
20
+ return bare(text).toLowerCase() === bare(href).toLowerCase();
21
+ }
22
+ const LinkCheckDialog = lazy(() => import("../lib/link-check-dialog.js").then((module) => ({ default: module.LinkCheckDialog })));
23
+ /**
24
+ * An external address waiting for the person: the whole of it, then Open (as the link would have,
25
+ * in a new tab with no opener), Copy or Cancel. `trigger` is what they clicked. The dialog loads where a
26
+ * check renders, so a surface that only opens links doesn't carry it; a click while it loads opens it once
27
+ * it has, and the link never leaves unseen.
28
+ */
29
+ export function ExternalLinkCheck({ href, trigger, }) {
30
+ const [asked, setAsked] = useState(false);
31
+ return (_jsx(Suspense, { fallback: trigger(() => setAsked(true)), children: _jsx(LinkCheckDialog, { href: href, trigger: trigger, asked: asked }) }));
32
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Plain text (tool output, terminal transcripts) with its web URLs as links: a dev server's
3
+ * `http://localhost:8000/` is one click away. Clicks are the host's to route (an app may open them in a
4
+ * pane of its own, ⌘/Ctrl-click in the system browser); without a handler they open in a new tab.
5
+ */
6
+ export type TextPart = {
7
+ readonly text: string;
8
+ } | {
9
+ readonly url: string;
10
+ };
11
+ /** Text split into plain runs and web URLs, in order. */
12
+ export declare function linkParts(text: string): TextPart[];
13
+ export declare function LinkedText({ text }: {
14
+ readonly text: string;
15
+ }): string | (string | import("react").JSX.Element)[];
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { keyed } from "../lib/keyed.js";
4
+ import { TextLink } from "./text-link.js";
5
+ /**
6
+ * A web URL runs until a space, a quote or angle bracket, or a character no URL holds unescaped: a
7
+ * letter of a script written without spaces (`详见 https://x/docs,然后`) or full-width punctuation.
8
+ */
9
+ const URL_PATTERN = /\bhttps?:\/\/[^\s<>"'`\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\u3000-\u303f\uff00-\uffef]+/giu;
10
+ /** Punctuation that ends a sentence rather than the URL (`see http://localhost:3000.`). */
11
+ const TRAILING = /[.,;:!?'"]+$/u;
12
+ /** Drops trailing punctuation, and closing brackets the URL didn't open (`(http://x/a)`). */
13
+ function trimUrl(raw) {
14
+ let url = raw.replace(TRAILING, "");
15
+ for (const [open, close] of [
16
+ ["(", ")"],
17
+ ["[", "]"],
18
+ ["{", "}"],
19
+ ]) {
20
+ while (url.endsWith(close) && url.split(close).length > url.split(open).length) {
21
+ url = url.slice(0, -1).replace(TRAILING, "");
22
+ }
23
+ }
24
+ return url;
25
+ }
26
+ /** Text split into plain runs and web URLs, in order. */
27
+ export function linkParts(text) {
28
+ const parts = [];
29
+ let at = 0;
30
+ for (const match of text.matchAll(URL_PATTERN)) {
31
+ const url = trimUrl(match[0]);
32
+ const start = match.index;
33
+ if (url.length <= "https://".length)
34
+ continue;
35
+ if (start > at)
36
+ parts.push({ text: text.slice(at, start) });
37
+ parts.push({ url });
38
+ at = start + url.length;
39
+ }
40
+ if (at < text.length)
41
+ parts.push({ text: text.slice(at) });
42
+ return parts;
43
+ }
44
+ export function LinkedText({ text }) {
45
+ if (!text.includes("http"))
46
+ return text;
47
+ return keyed(linkParts(text), (part) => ("url" in part ? part.url : part.text)).map(({ item: part, key }) => "url" in part ? (_jsx(TextLink, { href: part.url, target: "_blank", rel: "noreferrer", children: part.url }, key)) : (part.text));
48
+ }
@@ -0,0 +1,18 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ export interface LoadingRegionProps extends Omit<ComponentProps<"div">, "children" | "role" | "aria-busy" | "aria-label"> {
3
+ /** What is on its way, in words ("Loading changes"): the region's name for assistive technology. */
4
+ readonly label: string;
5
+ /** Placeholder rows of varied width, one per line of a list (files, changes, computers). */
6
+ readonly rows?: number;
7
+ /** Each row starts with an icon's square, as a file list's or a tree's rows do. */
8
+ readonly icon?: boolean;
9
+ /** The placeholder's own shape, after any rows: a grid of figures, a chart, a board's columns. */
10
+ readonly children?: ReactNode;
11
+ }
12
+ /**
13
+ * Where something is loading: a busy status region named by `label`, holding skeletons in the shape
14
+ * of what is coming. Mount it while the load runs and replace it with what arrives. It takes its
15
+ * place at once but shows only once the wait is long enough to notice (`useDelayedLoading`), so a
16
+ * quick load never flashes it and nothing moves when it does show.
17
+ */
18
+ export declare function LoadingRegion({ label, rows, icon, className, children, ...props }: LoadingRegionProps): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ import { useDelayedLoading } from "../lib/use-delayed-loading.js";
5
+ import { Skeleton } from "./skeleton.js";
6
+ /** Row widths, varied so a placeholder list reads as names of different lengths, not a ruled block. */
7
+ const ROW_WIDTHS = ["w-3/4", "w-1/2", "w-2/3", "w-2/5", "w-3/5", "w-1/3", "w-1/2"];
8
+ /**
9
+ * Where something is loading: a busy status region named by `label`, holding skeletons in the shape
10
+ * of what is coming. Mount it while the load runs and replace it with what arrives. It takes its
11
+ * place at once but shows only once the wait is long enough to notice (`useDelayedLoading`), so a
12
+ * quick load never flashes it and nothing moves when it does show.
13
+ */
14
+ export function LoadingRegion({ label, rows = 0, icon = false, className, children, ...props }) {
15
+ const shown = useDelayedLoading(true);
16
+ return (_jsxs("div", { "data-slot": "loading-region", role: "status", "aria-busy": "true", "aria-label": label, className: cn("flex flex-col gap-2 transition-opacity duration-(--duration-fast) ease-standard motion-reduce:transition-none", !shown && "opacity-0", className), ...props, children: [Array.from({ length: rows }, (_, i) => i).map((row) => (_jsxs("div", { className: "flex h-7 items-center gap-2", children: [icon && _jsx(Skeleton, { className: "size-4" }), _jsx(Skeleton, { className: cn("h-3", ROW_WIDTHS[row % ROW_WIDTHS.length]) })] }, row))), children] }));
17
+ }
@@ -0,0 +1,34 @@
1
+ import { type ComponentProps, type ReactNode } from "react";
2
+ /**
3
+ * Splits markdown into top-level blocks (paragraphs, headings, lists, fences, tables, …) so each can
4
+ * be memoized: while streaming only the last block re-parses. Splits at blank lines outside fences,
5
+ * keeps loose lists and indented continuations together, and isolates headings and fences.
6
+ */
7
+ export declare function splitMarkdownBlocks(source: string): string[];
8
+ /** A finished fenced code block: its text (without the closing newline) and its language tag. */
9
+ export interface MarkdownCodeBlock {
10
+ readonly code: string;
11
+ readonly lang: string | undefined;
12
+ }
13
+ /**
14
+ * Extra controls a host shows in a finished code block's header (a Run button). A block with
15
+ * any soft-wraps, so nothing they act on sits out of sight past the block's edge.
16
+ */
17
+ export type CodeBlockActions = (block: MarkdownCodeBlock) => ReactNode;
18
+ export interface MarkdownProps extends Omit<ComponentProps<"div">, "children"> {
19
+ /** Markdown source. */
20
+ children: string;
21
+ /** The text is still arriving: repairs unfinished syntax and only re-renders the last block. */
22
+ streaming?: boolean;
23
+ /** Constrain line length to a comfortable ~72ch measure. */
24
+ measure?: boolean;
25
+ /**
26
+ * Remote image hosts trusted to load on render (e.g. the host's own CDN). Other http(s) images
27
+ * show a "Load image" placeholder until the reader asks; data:, blob: and same-origin always load.
28
+ */
29
+ allowImageHosts?: readonly string[] | undefined;
30
+ /** Extra controls for each finished code block (none by default). */
31
+ codeActions?: CodeBlockActions | undefined;
32
+ }
33
+ /** Assistant-message renderer: GFM, Shiki code blocks, calm prose rhythm, streaming-safe. */
34
+ export declare function Markdown({ children, streaming, measure, allowImageHosts, codeActions, className, ...props }: MarkdownProps): import("react").JSX.Element;