@upbound/monarch-blocks 0.8.0 → 0.8.2

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.
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { k as CompileResult, t as CelValidationMeta, d as CelFilterCondition, f as CelFilterSchema, V as ValidationIssue, g as CelFilterTree, P as PillActiveSurface, u as CelFilterType, h as CelOperator, v as CelFilterShape, C as CelColumnDefinition, w as CelEmitOn, D as DateCelValue, M as MapKeyCelValue, l as MultiTextCelValue, m as RelativeDurationPreset, c as CelFilterBarProps } from './types-BULiU2qC.js';
3
- export { a as CelColumnOption, b as CelConditionValue, e as CelFilterGroup, i as CelRestrictedOptionSelectPayload, j as CheckboxCelValue, R as RadioCelValue, n as isCelFilterCondition, o as isCelFilterGroup, p as isCheckboxCelValue, q as isDateCelValue, r as isMapKeyCelValue, s as isMultiTextCelValue } from './types-BULiU2qC.js';
2
+ import { k as CompileResult, t as CelValidationMeta, d as CelFilterCondition, f as CelFilterSchema, V as ValidationIssue, g as CelFilterTree, P as PillActiveSurface, u as CelFilterType, h as CelOperator, v as CelFilterShape, C as CelColumnDefinition, w as CelEmitOn, D as DateCelValue, M as MapKeyCelValue, l as MultiTextCelValue, m as RelativeDurationPreset, c as CelFilterBarProps } from './types-D3SmFR8j.js';
3
+ export { a as CelColumnOption, b as CelConditionValue, e as CelFilterGroup, i as CelRestrictedOptionSelectPayload, j as CheckboxCelValue, R as RadioCelValue, n as isCelFilterCondition, o as isCelFilterGroup, p as isCheckboxCelValue, q as isDateCelValue, r as isMapKeyCelValue, s as isMultiTextCelValue } from './types-D3SmFR8j.js';
4
4
 
5
5
  /**
6
6
  * Applies draft/commit display rules to a raw compile result.
package/dist/chart.d.ts CHANGED
@@ -48,4 +48,5 @@ declare function ChartDescription({ className, ...props }: React.ComponentProps<
48
48
  declare function ChartAction({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
49
49
  declare function ChartFooter({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
50
50
 
51
- export { ChartAction, type ChartConfig, ChartContainer, ChartDescription, ChartFooter, ChartHeader, ChartLegend, ChartLegendContent, ChartStyle, ChartTitle, ChartTooltip, ChartTooltipContent };
51
+ export { ChartAction, ChartContainer, ChartDescription, ChartFooter, ChartHeader, ChartLegend, ChartLegendContent, ChartStyle, ChartTitle, ChartTooltip, ChartTooltipContent };
52
+ export type { ChartConfig };
@@ -139,4 +139,5 @@ interface ChatInterfaceProps extends ChatThreadProps, ChatNavRailProps {
139
139
  }
140
140
  declare function ChatInterface({ sessions, activeSessionId, onSelectSession, onNewSession, onOpenRoutines, onOpenConnectors, onOpenCustomize, className, ...threadProps }: ChatInterfaceProps): React.JSX.Element;
141
141
 
142
- export { ChatInput, type ChatInputProps, ChatInterface, type ChatInterfaceProps, type ChatMessage, ChatMessageBubble, type ChatRoutine, type ChatSession, ChatSessionList, type ChatSessionListProps, ChatThread, type ChatThreadProps, type ChatToolCall };
142
+ export { ChatInput, ChatInterface, ChatMessageBubble, ChatSessionList, ChatThread };
143
+ export type { ChatInputProps, ChatInterfaceProps, ChatMessage, ChatRoutine, ChatSession, ChatSessionListProps, ChatThreadProps, ChatToolCall };
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- declare const CODE_BLOCK_LANGUAGES: readonly ["tsx", "typescript", "bash", "shell", "json", "css", "yaml"];
3
+ declare const CODE_BLOCK_LANGUAGES: readonly ["tsx", "typescript", "bash", "shell", "json", "css", "yaml", "python"];
4
4
  type CodeBlockLanguage = (typeof CODE_BLOCK_LANGUAGES)[number];
5
5
 
6
6
  type CodeBlockCodeBase = {
@@ -22,9 +22,11 @@ declare function CodeBlock({ className, variant, size, ...props }: React.Compone
22
22
  size?: 'default' | 'sm';
23
23
  }): React.JSX.Element;
24
24
  declare function CodeBlockHeader({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
25
- declare function CodeBlockBody({ className, code, maxHeight, copyButton, downloadButton, ...props }: Omit<React.ComponentProps<'div'>, 'children'> & {
25
+ declare function CodeBlockBody({ className, code, maxHeight, searchable, copyButton, downloadButton, ...props }: Omit<React.ComponentProps<'div'>, 'children'> & {
26
26
  code: CodeBlockCode;
27
27
  maxHeight?: string;
28
+ /** Adds a search button to the overlay buttons that opens a find-in-code bar above the code. */
29
+ searchable?: boolean;
28
30
  copyButton?: React.ReactNode;
29
31
  downloadButton?: React.ReactNode;
30
32
  }): React.JSX.Element;
@@ -38,4 +40,5 @@ declare function CodeBlockDownloadButton({ code, fileName, mimeType, className,
38
40
  mimeType?: string;
39
41
  }): React.JSX.Element;
40
42
 
41
- export { CodeBlock, CodeBlockBody, type CodeBlockCode, CodeBlockCopyButton, CodeBlockDownloadButton, CodeBlockFooter, CodeBlockHeader, type CodeBlockLanguage };
43
+ export { CodeBlock, CodeBlockBody, CodeBlockCopyButton, CodeBlockDownloadButton, CodeBlockFooter, CodeBlockHeader };
44
+ export type { CodeBlockCode, CodeBlockLanguage };
@@ -32,7 +32,7 @@ var __objRest = (source, exclude) => {
32
32
  };
33
33
 
34
34
  // src/code-block.tsx
35
- import { useEffect, useState } from "react";
35
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
36
36
  import { Icon } from "@upbound/monarch-core";
37
37
 
38
38
  // src/lib/utils.ts
@@ -44,14 +44,67 @@ import {
44
44
  CardContent,
45
45
  CardFooter,
46
46
  CardHeader,
47
+ InputGroup,
48
+ InputGroupAddon,
49
+ InputGroupButton,
50
+ InputGroupInput,
51
+ InputGroupText,
47
52
  Tooltip,
48
53
  TooltipContent,
49
54
  TooltipTrigger
50
55
  } from "@upbound/monarch-core";
51
56
 
57
+ // src/lib/find-text.ts
58
+ function findTextRanges(root, query) {
59
+ var _a, _b;
60
+ const needle = query.toLowerCase();
61
+ if (!needle) return [];
62
+ const doc = (_a = root.ownerDocument) != null ? _a : document;
63
+ const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
64
+ const nodes = [];
65
+ const starts = [];
66
+ let text = "";
67
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
68
+ nodes.push(node);
69
+ starts.push(text.length);
70
+ text += (_b = node.nodeValue) != null ? _b : "";
71
+ }
72
+ let nodeIndex = 0;
73
+ const locate = (offset, isEnd) => {
74
+ while (nodeIndex < nodes.length - 1 && (isEnd ? starts[nodeIndex + 1] < offset : starts[nodeIndex + 1] <= offset)) {
75
+ nodeIndex++;
76
+ }
77
+ return [nodes[nodeIndex], offset - starts[nodeIndex]];
78
+ };
79
+ const haystack = text.toLowerCase();
80
+ const ranges = [];
81
+ for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + needle.length)) {
82
+ const range = doc.createRange();
83
+ range.setStart(...locate(at, false));
84
+ range.setEnd(...locate(at + needle.length, true));
85
+ ranges.push(range);
86
+ }
87
+ return ranges;
88
+ }
89
+
90
+ // src/lib/search-input.ts
91
+ var SEARCH_INPUT_PROPS = {
92
+ type: "search",
93
+ autoComplete: "off",
94
+ "data-1p-ignore": true,
95
+ // 1Password
96
+ "data-lpignore": "true",
97
+ // LastPass
98
+ "data-bwignore": true,
99
+ // Bitwarden
100
+ "data-form-type": "other"
101
+ // Dashlane
102
+ };
103
+ var SEARCH_INPUT_CLASS = "[&::-webkit-search-cancel-button]:appearance-none";
104
+
52
105
  // src/lib/highlight.ts
53
106
  import { createHighlighter } from "shiki";
54
- var CODE_BLOCK_LANGUAGES = ["tsx", "typescript", "bash", "shell", "json", "css", "yaml"];
107
+ var CODE_BLOCK_LANGUAGES = ["tsx", "typescript", "bash", "shell", "json", "css", "yaml", "python"];
55
108
  var HIGHLIGHTER_KEY = "__monarch_shiki_highlighter__";
56
109
  function getHighlighter() {
57
110
  const g = globalThis;
@@ -130,23 +183,84 @@ var LINE_NUMBER_GUTTER = cn(
130
183
  "[&_.line]:before:text-[0.75rem]",
131
184
  "[&_.line]:before:select-none"
132
185
  );
186
+ var SHIKI_DARK_THEME = [
187
+ "dark:[&_.shiki]:text-(--shiki-dark)!",
188
+ "dark:[&_.shiki_span]:text-(--shiki-dark)!",
189
+ "dark:[&_.shiki_span]:[font-style:var(--shiki-dark-font-style)]!",
190
+ "dark:[&_.shiki_span]:[font-weight:var(--shiki-dark-font-weight)]!",
191
+ "dark:[&_.shiki_span]:[text-decoration:var(--shiki-dark-text-decoration)]!"
192
+ ];
193
+ var CODE_BLOCK_ICON_BUTTON = cn(
194
+ "border-border bg-background inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border motion-safe:transition-colors",
195
+ "text-muted-foreground hover:bg-accent hover:text-foreground",
196
+ "focus-visible:ring-ring/30 focus-visible:ring-2 focus-visible:outline-none",
197
+ "group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted group-data-[variant=terminal]/code-block:hover:bg-terminal-muted-hover-bg group-data-[variant=terminal]/code-block:hover:text-terminal-muted-hover-fg"
198
+ );
199
+ function supportsHighlights() {
200
+ return typeof CSS !== "undefined" && "highlights" in CSS && typeof Highlight !== "undefined";
201
+ }
202
+ function useCodeSearch(codeRef, query, renderedCode) {
203
+ const id = useId().replace(/[^a-zA-Z0-9_-]/g, "");
204
+ const names = { all: `code-find-${id}`, current: `code-find-${id}-current` };
205
+ const [ranges, setRanges] = useState([]);
206
+ const [current, setCurrent] = useState(0);
207
+ useEffect(() => {
208
+ const root = codeRef.current;
209
+ const next = root ? findTextRanges(root, query) : [];
210
+ setRanges(next);
211
+ setCurrent(0);
212
+ if (!supportsHighlights() || next.length === 0) return;
213
+ CSS.highlights.set(names.all, new Highlight(...next));
214
+ return () => {
215
+ CSS.highlights.delete(names.all);
216
+ };
217
+ }, [codeRef, query, renderedCode, names.all]);
218
+ useEffect(() => {
219
+ var _a, _b;
220
+ const range = ranges[current];
221
+ if (!range) return;
222
+ (_b = (_a = range.startContainer.parentElement) == null ? void 0 : _a.scrollIntoView) == null ? void 0 : _b.call(_a, { block: "nearest", inline: "nearest" });
223
+ if (!supportsHighlights()) return;
224
+ CSS.highlights.set(names.current, new Highlight(range));
225
+ return () => {
226
+ CSS.highlights.delete(names.current);
227
+ };
228
+ }, [ranges, current, names.current]);
229
+ const step = (by) => setCurrent((i) => ranges.length ? (i + by + ranges.length) % ranges.length : 0);
230
+ return {
231
+ count: ranges.length,
232
+ current,
233
+ next: () => step(1),
234
+ previous: () => step(-1),
235
+ highlightCss: `::highlight(${names.all}){background-color:color-mix(in oklch,var(--status-warning) 30%,transparent)}::highlight(${names.current}){background-color:color-mix(in oklch,var(--status-warning) 65%,transparent)}`
236
+ };
237
+ }
133
238
  function CodeBlockBody(_a) {
134
239
  var _b = _a, {
135
240
  className,
136
241
  code,
137
242
  maxHeight,
243
+ searchable = false,
138
244
  copyButton,
139
245
  downloadButton
140
246
  } = _b, props = __objRest(_b, [
141
247
  "className",
142
248
  "code",
143
249
  "maxHeight",
250
+ "searchable",
144
251
  "copyButton",
145
252
  "downloadButton"
146
253
  ]);
147
254
  const { content, lineNumbers } = code;
148
255
  const [html, setHtml] = useState(null);
149
256
  const language = code.type === "parsed" ? code.language : null;
257
+ const [searchOpen, setSearchOpen] = useState(false);
258
+ const [query, setQuery] = useState("");
259
+ const codeRef = useRef(null);
260
+ const searchInputRef = useRef(null);
261
+ const searchInputId = `code-search-${useId()}`;
262
+ const searchButtonRef = useRef(null);
263
+ const search = useCodeSearch(codeRef, searchOpen ? query : "", html != null ? html : content);
150
264
  useEffect(() => {
151
265
  if (!language) {
152
266
  setHtml(null);
@@ -160,32 +274,127 @@ function CodeBlockBody(_a) {
160
274
  cancelled = true;
161
275
  };
162
276
  }, [content, language]);
163
- return /* @__PURE__ */ jsxs(CardContent, __spreadProps(__spreadValues({ "data-line-numbers": lineNumbers || void 0, className: cn("relative p-0!", className) }, props), { children: [
164
- (copyButton || downloadButton) && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 z-10 flex items-center gap-1 opacity-0 transition-opacity group-hover/code-block:opacity-100", children: [
165
- copyButton,
166
- downloadButton
167
- ] }),
168
- /* @__PURE__ */ jsx(
169
- "div",
277
+ const openSearch = () => {
278
+ var _a2, _b2;
279
+ setSearchOpen(true);
280
+ (_a2 = searchInputRef.current) == null ? void 0 : _a2.focus();
281
+ (_b2 = searchInputRef.current) == null ? void 0 : _b2.select();
282
+ };
283
+ const closeSearch = useCallback(() => {
284
+ var _a2;
285
+ setSearchOpen(false);
286
+ setQuery("");
287
+ (_a2 = searchButtonRef.current) == null ? void 0 : _a2.focus();
288
+ }, []);
289
+ useEffect(() => {
290
+ if (!searchOpen) return;
291
+ const onKeyDown = (event) => {
292
+ if (event.key !== "Escape" || event.target !== searchInputRef.current) return;
293
+ event.preventDefault();
294
+ event.stopPropagation();
295
+ closeSearch();
296
+ };
297
+ window.addEventListener("keydown", onKeyDown, { capture: true });
298
+ return () => window.removeEventListener("keydown", onKeyDown, { capture: true });
299
+ }, [searchOpen, closeSearch]);
300
+ const searchButton = searchable && /* @__PURE__ */ jsxs(Tooltip, { children: [
301
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
302
+ "button",
170
303
  {
171
- className: cn("overflow-x-auto", maxHeight && "overflow-y-auto"),
172
- style: maxHeight ? { maxHeight } : void 0,
173
- children: /* @__PURE__ */ jsx(
174
- "div",
175
- {
176
- className: cn(
177
- "[&_code]:font-mono [&_pre]:m-0 [&_pre]:p-4 group-data-[size=sm]/code-block:[&_pre]:p-3",
178
- html && "[&_pre]:bg-transparent!",
179
- lineNumbers && LINE_NUMBER_GUTTER
180
- ),
181
- children: html ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }) : /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: lineNumbers ? void 0 : "whitespace-pre", children: lineNumbers ? content.split("\n").map((line, i) => /* @__PURE__ */ jsxs("span", { className: "line", children: [
182
- line,
183
- "\n"
184
- ] }, i)) : content }) })
185
- }
186
- )
304
+ ref: searchButtonRef,
305
+ type: "button",
306
+ "data-slot": "code-block-search-button",
307
+ "aria-label": "Search",
308
+ "aria-expanded": searchOpen,
309
+ onClick: openSearch,
310
+ className: CODE_BLOCK_ICON_BUTTON,
311
+ children: /* @__PURE__ */ jsx(Icon, { name: "magnifying-glass", size: "sm" })
187
312
  }
188
- )
313
+ ) }),
314
+ /* @__PURE__ */ jsx(TooltipContent, { children: "Search" })
315
+ ] });
316
+ return /* @__PURE__ */ jsxs(CardContent, __spreadProps(__spreadValues({ "data-line-numbers": lineNumbers || void 0, className: cn("relative p-0!", className) }, props), { children: [
317
+ searchable && searchOpen && /* @__PURE__ */ jsxs("div", { "data-slot": "code-block-search", className: "p-2", children: [
318
+ /* @__PURE__ */ jsx("style", { children: search.highlightCss }),
319
+ /* @__PURE__ */ jsxs(InputGroup, { children: [
320
+ /* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(Icon, { name: "magnifying-glass" }) }),
321
+ /* @__PURE__ */ jsx(
322
+ InputGroupInput,
323
+ __spreadProps(__spreadValues({
324
+ ref: searchInputRef,
325
+ autoFocus: true
326
+ }, SEARCH_INPUT_PROPS), {
327
+ id: searchInputId,
328
+ className: SEARCH_INPUT_CLASS,
329
+ placeholder: "Find\u2026",
330
+ "aria-label": "Find in code",
331
+ value: query,
332
+ onChange: (e) => setQuery(e.target.value),
333
+ onKeyDown: (e) => {
334
+ if (e.key === "Enter") {
335
+ e.preventDefault();
336
+ if (e.shiftKey) search.previous();
337
+ else search.next();
338
+ }
339
+ }
340
+ })
341
+ ),
342
+ /* @__PURE__ */ jsxs(InputGroupAddon, { align: "inline-end", children: [
343
+ /* @__PURE__ */ jsx(InputGroupText, { "aria-live": "polite", className: "text-caption tabular-nums", children: query ? search.count ? `${search.current + 1} of ${search.count}` : "No results" : null }),
344
+ /* @__PURE__ */ jsx(
345
+ InputGroupButton,
346
+ {
347
+ size: "icon-xs",
348
+ "aria-label": "Previous match",
349
+ disabled: search.count === 0,
350
+ onClick: search.previous,
351
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-up" })
352
+ }
353
+ ),
354
+ /* @__PURE__ */ jsx(
355
+ InputGroupButton,
356
+ {
357
+ size: "icon-xs",
358
+ "aria-label": "Next match",
359
+ disabled: search.count === 0,
360
+ onClick: search.next,
361
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-down" })
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx(InputGroupButton, { size: "icon-xs", "aria-label": "Close search", onClick: closeSearch, children: /* @__PURE__ */ jsx(Icon, { name: "xmark" }) })
365
+ ] })
366
+ ] })
367
+ ] }),
368
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
369
+ (searchButton || copyButton || downloadButton) && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 z-10 flex items-center gap-1 opacity-0 transition-opacity group-focus-within/code-block:opacity-100 group-hover/code-block:opacity-100", children: [
370
+ searchButton,
371
+ copyButton,
372
+ downloadButton
373
+ ] }),
374
+ /* @__PURE__ */ jsx(
375
+ "div",
376
+ {
377
+ className: cn("overflow-x-auto", maxHeight && "overflow-y-auto"),
378
+ style: maxHeight ? { maxHeight } : void 0,
379
+ children: /* @__PURE__ */ jsx(
380
+ "div",
381
+ {
382
+ ref: codeRef,
383
+ className: cn(
384
+ "[&_code]:font-mono [&_pre]:m-0 [&_pre]:p-4 group-data-[size=sm]/code-block:[&_pre]:p-3",
385
+ html && "[&_pre]:bg-transparent!",
386
+ html && SHIKI_DARK_THEME,
387
+ lineNumbers && LINE_NUMBER_GUTTER
388
+ ),
389
+ children: html ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }) : /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: lineNumbers ? void 0 : "whitespace-pre", children: lineNumbers ? content.split("\n").map((line, i) => /* @__PURE__ */ jsxs("span", { className: "line", children: [
390
+ line,
391
+ "\n"
392
+ ] }, i)) : content }) })
393
+ }
394
+ )
395
+ }
396
+ )
397
+ ] })
189
398
  ] }));
190
399
  }
191
400
  function CodeBlockFooter(_a) {
@@ -220,13 +429,7 @@ function CodeBlockCopyButton(_a) {
220
429
  __spreadProps(__spreadValues({
221
430
  "data-slot": "code-block-copy-button",
222
431
  onClick: handleCopy,
223
- className: cn(
224
- "border-border bg-background inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border motion-safe:transition-colors",
225
- "text-muted-foreground hover:bg-accent hover:text-foreground",
226
- "focus-visible:ring-ring/30 focus-visible:ring-2 focus-visible:outline-none",
227
- "group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted group-data-[variant=terminal]/code-block:hover:bg-terminal-muted-hover-bg group-data-[variant=terminal]/code-block:hover:text-terminal-muted-hover-fg",
228
- className
229
- ),
432
+ className: cn(CODE_BLOCK_ICON_BUTTON, className),
230
433
  "aria-label": copied ? "Copied" : "Copy code"
231
434
  }, props), {
232
435
  children: copied ? /* @__PURE__ */ jsx(Icon, { name: "check", size: "sm", className: "text-green-500" }) : /* @__PURE__ */ jsx(Icon, { name: "copy", size: "sm" })
@@ -267,13 +470,7 @@ function CodeBlockDownloadButton(_a) {
267
470
  __spreadProps(__spreadValues({
268
471
  "data-slot": "code-block-download-button",
269
472
  onClick: handleDownload,
270
- className: cn(
271
- "border-border bg-background inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border motion-safe:transition-colors",
272
- "text-muted-foreground hover:bg-accent hover:text-foreground",
273
- "focus-visible:ring-ring/30 focus-visible:ring-2 focus-visible:outline-none",
274
- "group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted group-data-[variant=terminal]/code-block:hover:bg-terminal-muted-hover-bg group-data-[variant=terminal]/code-block:hover:text-terminal-muted-hover-fg",
275
- className
276
- ),
473
+ className: cn(CODE_BLOCK_ICON_BUTTON, className),
277
474
  "aria-label": downloaded ? "Downloaded" : "Download code"
278
475
  }, props), {
279
476
  children: downloaded ? /* @__PURE__ */ jsx(Icon, { name: "check", size: "sm", className: "text-green-500" }) : /* @__PURE__ */ jsx(Icon, { name: "download", size: "sm" })
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/code-block.tsx","../src/lib/utils.ts","../src/lib/highlight.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useEffect, useState } from 'react';\nimport { Icon } from '@upbound/monarch-core';\n\nimport { cn } from '@/lib/utils';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from '@upbound/monarch-core';\nimport { highlightCode, type CodeBlockLanguage } from './lib/highlight';\n\nexport type { CodeBlockLanguage } from './lib/highlight';\n\ntype CodeBlockCodeBase = {\n /** Raw code string — used for clipboard copy, download, and as the rendered text. */\n content: string;\n /** Numbers each line in the gutter. */\n lineNumbers?: boolean;\n};\n\nexport type CodeBlockCode =\n | (CodeBlockCodeBase & {\n /** Syntax-highlighted with Shiki inside CodeBlockBody — no highlighting step needed on your end. */\n type: 'parsed';\n language: CodeBlockLanguage;\n })\n | (CodeBlockCodeBase & {\n /** Rendered as plain, unhighlighted text. */\n type: 'raw';\n });\n\nfunction CodeBlock({\n className,\n variant = 'default',\n size = 'default',\n ...props\n}: React.ComponentProps<'div'> & {\n variant?: 'default' | 'terminal';\n size?: 'default' | 'sm';\n}) {\n return (\n <Card\n data-slot=\"code-block\"\n data-variant={variant}\n data-size={size}\n role=\"region\"\n className={cn(\n 'group/code-block gap-0 overflow-hidden py-0',\n size === 'default' && 'text-[13px]',\n size === 'sm' && 'text-body-sm',\n variant === 'terminal' && 'border-terminal-border bg-terminal-bg text-terminal-fg ring-0',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CodeBlockHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <CardHeader\n className={cn(\n 'group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted flex flex-row items-center gap-2 border-b bg-[color-mix(in_oklch,var(--muted)_50%,var(--card))] py-4',\n className,\n )}\n {...props}\n />\n );\n}\n\nconst LINE_NUMBER_GUTTER = cn(\n '[&_code]:[counter-reset:line]',\n '[&_.line]:before:[counter-increment:line]',\n '[&_.line]:before:content-[counter(line)]',\n '[&_.line]:before:inline-block',\n '[&_.line]:before:w-6',\n '[&_.line]:before:mr-3',\n '[&_.line]:before:text-right',\n '[&_.line]:before:text-muted-foreground',\n '[&_.line]:before:text-[0.75rem]',\n '[&_.line]:before:select-none',\n);\n\nfunction CodeBlockBody({\n className,\n code,\n maxHeight,\n copyButton,\n downloadButton,\n ...props\n}: Omit<React.ComponentProps<'div'>, 'children'> & {\n code: CodeBlockCode;\n maxHeight?: string;\n copyButton?: React.ReactNode;\n downloadButton?: React.ReactNode;\n}) {\n const { content, lineNumbers } = code;\n const [html, setHtml] = useState<string | null>(null);\n const language = code.type === 'parsed' ? code.language : null;\n\n useEffect(() => {\n if (!language) {\n setHtml(null);\n return;\n }\n\n let cancelled = false;\n highlightCode(content, language).then(result => {\n if (!cancelled) setHtml(result);\n });\n\n return () => {\n cancelled = true;\n };\n }, [content, language]);\n\n // Layout note: the copy/download buttons must stay anchored to the\n // top-right of the visible code area when the content scrolls\n // horizontally. To make that work the relative containing block\n // (CardContent) must NOT be the scrollable element — otherwise\n // `right`-positioning is computed against the scrolled content edge and\n // the buttons move with the scroll. So the overflow lives on an inner\n // wrapper and the absolute button group is its sibling under CardContent.\n return (\n <CardContent data-line-numbers={lineNumbers || undefined} className={cn('relative p-0!', className)} {...props}>\n {(copyButton || downloadButton) && (\n <div className=\"absolute top-2 right-2 z-10 flex items-center gap-1 opacity-0 transition-opacity group-hover/code-block:opacity-100\">\n {copyButton}\n {downloadButton}\n </div>\n )}\n <div\n className={cn('overflow-x-auto', maxHeight && 'overflow-y-auto')}\n style={maxHeight ? { maxHeight } : undefined}\n >\n <div\n className={cn(\n '[&_code]:font-mono [&_pre]:m-0 [&_pre]:p-4 group-data-[size=sm]/code-block:[&_pre]:p-3',\n html && '[&_pre]:bg-transparent!',\n lineNumbers && LINE_NUMBER_GUTTER,\n )}\n >\n {html ? (\n <div dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n <pre>\n <code className={lineNumbers ? undefined : 'whitespace-pre'}>\n {lineNumbers\n ? content.split('\\n').map((line, i) => (\n <span key={i} className=\"line\">\n {line}\n {'\\n'}\n </span>\n ))\n : content}\n </code>\n </pre>\n )}\n </div>\n </div>\n </CardContent>\n );\n}\n\nfunction CodeBlockFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <CardFooter\n className={cn(\n 'text-body-sm group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-footer-bg group-data-[variant=terminal]/code-block:text-terminal-muted',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CodeBlockCopyButton({\n code,\n className,\n ...props\n}: Omit<React.ComponentProps<'button'>, 'children'> & {\n code: string;\n}) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n data-slot=\"code-block-copy-button\"\n onClick={handleCopy}\n className={cn(\n 'border-border bg-background inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border motion-safe:transition-colors',\n 'text-muted-foreground hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-ring/30 focus-visible:ring-2 focus-visible:outline-none',\n 'group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted group-data-[variant=terminal]/code-block:hover:bg-terminal-muted-hover-bg group-data-[variant=terminal]/code-block:hover:text-terminal-muted-hover-fg',\n className,\n )}\n aria-label={copied ? 'Copied' : 'Copy code'}\n {...props}\n >\n {copied ? <Icon name=\"check\" size=\"sm\" className=\"text-green-500\" /> : <Icon name=\"copy\" size=\"sm\" />}\n </button>\n </TooltipTrigger>\n <TooltipContent>{copied ? 'Copied' : 'Copy'}</TooltipContent>\n </Tooltip>\n );\n}\n\nfunction CodeBlockDownloadButton({\n code,\n fileName,\n mimeType = 'text/plain',\n className,\n ...props\n}: Omit<React.ComponentProps<'button'>, 'children'> & {\n code: string;\n fileName: string;\n mimeType?: string;\n}) {\n const [downloaded, setDownloaded] = useState(false);\n\n const handleDownload = () => {\n const blob = new Blob([code], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n setDownloaded(true);\n setTimeout(() => setDownloaded(false), 2000);\n };\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n data-slot=\"code-block-download-button\"\n onClick={handleDownload}\n className={cn(\n 'border-border bg-background inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border motion-safe:transition-colors',\n 'text-muted-foreground hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-ring/30 focus-visible:ring-2 focus-visible:outline-none',\n 'group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted group-data-[variant=terminal]/code-block:hover:bg-terminal-muted-hover-bg group-data-[variant=terminal]/code-block:hover:text-terminal-muted-hover-fg',\n className,\n )}\n aria-label={downloaded ? 'Downloaded' : 'Download code'}\n {...props}\n >\n {downloaded ? <Icon name=\"check\" size=\"sm\" className=\"text-green-500\" /> : <Icon name=\"download\" size=\"sm\" />}\n </button>\n </TooltipTrigger>\n <TooltipContent>{downloaded ? 'Downloaded' : 'Download'}</TooltipContent>\n </Tooltip>\n );\n}\n\nexport { CodeBlock, CodeBlockHeader, CodeBlockBody, CodeBlockFooter, CodeBlockCopyButton, CodeBlockDownloadButton };\n","export { cn } from '@upbound/monarch-core/utils';\n\nexport function sortBy<T>(items: T[], getKey: (item: T) => string): T[] {\n return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));\n}\n","import { createHighlighter, type Highlighter } from 'shiki';\n\nexport const CODE_BLOCK_LANGUAGES = ['tsx', 'typescript', 'bash', 'shell', 'json', 'css', 'yaml'] as const;\n\nexport type CodeBlockLanguage = (typeof CODE_BLOCK_LANGUAGES)[number];\n\nconst HIGHLIGHTER_KEY = '__monarch_shiki_highlighter__';\n\ntype HighlighterGlobal = typeof globalThis & {\n [HIGHLIGHTER_KEY]?: Promise<Highlighter>;\n};\n\n/**\n * Cache the in-flight Promise, not the resolved instance. `createHighlighter`\n * is async — storing only after `await` lets every concurrent CodeBlockBody\n * start a new highlighter (Shiki warns at 10/20/30 instances). `globalThis`\n * keeps the same promise across HMR module re-evals in the same realm.\n */\nfunction getHighlighter(): Promise<Highlighter> {\n const g = globalThis as HighlighterGlobal;\n if (!g[HIGHLIGHTER_KEY]) {\n g[HIGHLIGHTER_KEY] = createHighlighter({\n themes: ['github-light-high-contrast', 'github-dark-dimmed'],\n langs: [...CODE_BLOCK_LANGUAGES],\n }).catch(error => {\n delete g[HIGHLIGHTER_KEY];\n throw error;\n });\n }\n return g[HIGHLIGHTER_KEY];\n}\n\n/**\n * Highlight a code string with Shiki dual themes. Returns raw HTML safe to\n * render via dangerouslySetInnerHTML.\n *\n * Light theme renders as inline `color:` / `background-color:`.\n * Dark theme renders as CSS variables `--shiki-dark` / `--shiki-dark-bg`.\n */\nexport async function highlightCode(code: string, lang: CodeBlockLanguage): Promise<string> {\n const hl = await getHighlighter();\n return hl.codeToHtml(code, {\n lang,\n themes: {\n light: 'github-light-high-contrast',\n dark: 'github-dark-dimmed',\n },\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/code-block.tsx","../src/lib/utils.ts","../src/lib/find-text.ts","../src/lib/search-input.ts","../src/lib/highlight.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useCallback, useEffect, useId, useRef, useState } from 'react';\nimport { Icon } from '@upbound/monarch-core';\n\nimport { cn } from '@/lib/utils';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupInput,\n InputGroupText,\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from '@upbound/monarch-core';\nimport { findTextRanges } from './lib/find-text';\nimport { SEARCH_INPUT_CLASS, SEARCH_INPUT_PROPS } from './lib/search-input';\nimport { highlightCode, type CodeBlockLanguage } from './lib/highlight';\n\nexport type { CodeBlockLanguage } from './lib/highlight';\n\ntype CodeBlockCodeBase = {\n /** Raw code string — used for clipboard copy, download, and as the rendered text. */\n content: string;\n /** Numbers each line in the gutter. */\n lineNumbers?: boolean;\n};\n\nexport type CodeBlockCode =\n | (CodeBlockCodeBase & {\n /** Syntax-highlighted with Shiki inside CodeBlockBody — no highlighting step needed on your end. */\n type: 'parsed';\n language: CodeBlockLanguage;\n })\n | (CodeBlockCodeBase & {\n /** Rendered as plain, unhighlighted text. */\n type: 'raw';\n });\n\nfunction CodeBlock({\n className,\n variant = 'default',\n size = 'default',\n ...props\n}: React.ComponentProps<'div'> & {\n variant?: 'default' | 'terminal';\n size?: 'default' | 'sm';\n}) {\n return (\n <Card\n data-slot=\"code-block\"\n data-variant={variant}\n data-size={size}\n role=\"region\"\n className={cn(\n 'group/code-block gap-0 overflow-hidden py-0',\n size === 'default' && 'text-[13px]',\n size === 'sm' && 'text-body-sm',\n variant === 'terminal' && 'border-terminal-border bg-terminal-bg text-terminal-fg ring-0',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CodeBlockHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <CardHeader\n className={cn(\n 'group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted flex flex-row items-center gap-2 border-b bg-[color-mix(in_oklch,var(--muted)_50%,var(--card))] py-4',\n className,\n )}\n {...props}\n />\n );\n}\n\nconst LINE_NUMBER_GUTTER = cn(\n '[&_code]:[counter-reset:line]',\n '[&_.line]:before:[counter-increment:line]',\n '[&_.line]:before:content-[counter(line)]',\n '[&_.line]:before:inline-block',\n '[&_.line]:before:w-6',\n '[&_.line]:before:mr-3',\n '[&_.line]:before:text-right',\n '[&_.line]:before:text-muted-foreground',\n '[&_.line]:before:text-[0.75rem]',\n '[&_.line]:before:select-none',\n);\n\n// Shiki dual themes emit the light theme inline and the dark theme as --shiki-dark-* variables.\n// Nothing swaps them automatically, so under .dark point each property at its variable.\nconst SHIKI_DARK_THEME = [\n 'dark:[&_.shiki]:text-(--shiki-dark)!',\n 'dark:[&_.shiki_span]:text-(--shiki-dark)!',\n 'dark:[&_.shiki_span]:[font-style:var(--shiki-dark-font-style)]!',\n 'dark:[&_.shiki_span]:[font-weight:var(--shiki-dark-font-weight)]!',\n 'dark:[&_.shiki_span]:[text-decoration:var(--shiki-dark-text-decoration)]!',\n];\n\n// The overlay buttons (search, copy, download) share one look.\nconst CODE_BLOCK_ICON_BUTTON = cn(\n 'border-border bg-background inline-flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border motion-safe:transition-colors',\n 'text-muted-foreground hover:bg-accent hover:text-foreground',\n 'focus-visible:ring-ring/30 focus-visible:ring-2 focus-visible:outline-none',\n 'group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-bg group-data-[variant=terminal]/code-block:text-terminal-muted group-data-[variant=terminal]/code-block:hover:bg-terminal-muted-hover-bg group-data-[variant=terminal]/code-block:hover:text-terminal-muted-hover-fg',\n);\n\nfunction supportsHighlights() {\n return typeof CSS !== 'undefined' && 'highlights' in CSS && typeof Highlight !== 'undefined';\n}\n\n/**\n * Find-in-code state for one CodeBlockBody. Matches are painted with the CSS Custom Highlight\n * API, so the highlighted markup is never rewritten; browsers without it still get the match\n * count and navigation.\n */\nfunction useCodeSearch(codeRef: React.RefObject<HTMLElement | null>, query: string, renderedCode: unknown) {\n const id = useId().replace(/[^a-zA-Z0-9_-]/g, '');\n const names = { all: `code-find-${id}`, current: `code-find-${id}-current` };\n const [ranges, setRanges] = useState<Range[]>([]);\n const [current, setCurrent] = useState(0);\n\n useEffect(() => {\n const root = codeRef.current;\n const next = root ? findTextRanges(root, query) : [];\n setRanges(next);\n setCurrent(0);\n if (!supportsHighlights() || next.length === 0) return;\n CSS.highlights.set(names.all, new Highlight(...next));\n return () => {\n CSS.highlights.delete(names.all);\n };\n // `renderedCode` changes when Shiki's HTML replaces the plain text.\n }, [codeRef, query, renderedCode, names.all]);\n\n useEffect(() => {\n const range = ranges[current];\n if (!range) return;\n range.startContainer.parentElement?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });\n if (!supportsHighlights()) return;\n CSS.highlights.set(names.current, new Highlight(range));\n return () => {\n CSS.highlights.delete(names.current);\n };\n }, [ranges, current, names.current]);\n\n const step = (by: number) => setCurrent(i => (ranges.length ? (i + by + ranges.length) % ranges.length : 0));\n\n return {\n count: ranges.length,\n current,\n next: () => step(1),\n previous: () => step(-1),\n highlightCss: `::highlight(${names.all}){background-color:color-mix(in oklch,var(--status-warning) 30%,transparent)}::highlight(${names.current}){background-color:color-mix(in oklch,var(--status-warning) 65%,transparent)}`,\n };\n}\n\nfunction CodeBlockBody({\n className,\n code,\n maxHeight,\n searchable = false,\n copyButton,\n downloadButton,\n ...props\n}: Omit<React.ComponentProps<'div'>, 'children'> & {\n code: CodeBlockCode;\n maxHeight?: string;\n /** Adds a search button to the overlay buttons that opens a find-in-code bar above the code. */\n searchable?: boolean;\n copyButton?: React.ReactNode;\n downloadButton?: React.ReactNode;\n}) {\n const { content, lineNumbers } = code;\n const [html, setHtml] = useState<string | null>(null);\n const language = code.type === 'parsed' ? code.language : null;\n\n const [searchOpen, setSearchOpen] = useState(false);\n const [query, setQuery] = useState('');\n const codeRef = useRef<HTMLDivElement>(null);\n const searchInputRef = useRef<HTMLInputElement>(null);\n const searchInputId = `code-search-${useId()}`;\n const searchButtonRef = useRef<HTMLButtonElement>(null);\n const search = useCodeSearch(codeRef, searchOpen ? query : '', html ?? content);\n\n useEffect(() => {\n if (!language) {\n setHtml(null);\n return;\n }\n\n let cancelled = false;\n highlightCode(content, language).then(result => {\n if (!cancelled) setHtml(result);\n });\n\n return () => {\n cancelled = true;\n };\n }, [content, language]);\n\n const openSearch = () => {\n setSearchOpen(true);\n // Already open: move focus back to the field.\n searchInputRef.current?.focus();\n searchInputRef.current?.select();\n };\n\n const closeSearch = useCallback(() => {\n setSearchOpen(false);\n setQuery('');\n searchButtonRef.current?.focus();\n }, []);\n\n // Escape closes the search, not a surrounding dialog or sheet. Radix listens for Escape on the\n // document in the capture phase, so this has to run earlier, on the window.\n useEffect(() => {\n if (!searchOpen) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || event.target !== searchInputRef.current) return;\n event.preventDefault();\n event.stopPropagation();\n closeSearch();\n };\n window.addEventListener('keydown', onKeyDown, { capture: true });\n return () => window.removeEventListener('keydown', onKeyDown, { capture: true });\n }, [searchOpen, closeSearch]);\n\n const searchButton = searchable && (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n ref={searchButtonRef}\n type=\"button\"\n data-slot=\"code-block-search-button\"\n aria-label=\"Search\"\n aria-expanded={searchOpen}\n onClick={openSearch}\n className={CODE_BLOCK_ICON_BUTTON}\n >\n <Icon name=\"magnifying-glass\" size=\"sm\" />\n </button>\n </TooltipTrigger>\n <TooltipContent>Search</TooltipContent>\n </Tooltip>\n );\n\n // Layout note: the overlay buttons must stay anchored to the top-right of\n // the visible code area when the content scrolls horizontally. To make that\n // work their relative containing block must NOT be the scrollable element —\n // otherwise `right`-positioning is computed against the scrolled content\n // edge and the buttons move with the scroll. So the overflow lives on an\n // inner wrapper and the absolute button group is its sibling.\n return (\n <CardContent data-line-numbers={lineNumbers || undefined} className={cn('relative p-0!', className)} {...props}>\n {searchable && searchOpen && (\n <div data-slot=\"code-block-search\" className=\"p-2\">\n <style>{search.highlightCss}</style>\n <InputGroup>\n <InputGroupAddon>\n <Icon name=\"magnifying-glass\" />\n </InputGroupAddon>\n <InputGroupInput\n ref={searchInputRef}\n autoFocus\n {...SEARCH_INPUT_PROPS}\n id={searchInputId}\n className={SEARCH_INPUT_CLASS}\n placeholder=\"Find…\"\n aria-label=\"Find in code\"\n value={query}\n onChange={e => setQuery(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n e.preventDefault();\n if (e.shiftKey) search.previous();\n else search.next();\n }\n }}\n />\n <InputGroupAddon align=\"inline-end\">\n <InputGroupText aria-live=\"polite\" className=\"text-caption tabular-nums\">\n {query ? (search.count ? `${search.current + 1} of ${search.count}` : 'No results') : null}\n </InputGroupText>\n <InputGroupButton\n size=\"icon-xs\"\n aria-label=\"Previous match\"\n disabled={search.count === 0}\n onClick={search.previous}\n >\n <Icon name=\"chevron-up\" />\n </InputGroupButton>\n <InputGroupButton\n size=\"icon-xs\"\n aria-label=\"Next match\"\n disabled={search.count === 0}\n onClick={search.next}\n >\n <Icon name=\"chevron-down\" />\n </InputGroupButton>\n <InputGroupButton size=\"icon-xs\" aria-label=\"Close search\" onClick={closeSearch}>\n <Icon name=\"xmark\" />\n </InputGroupButton>\n </InputGroupAddon>\n </InputGroup>\n </div>\n )}\n <div className=\"relative\">\n {(searchButton || copyButton || downloadButton) && (\n <div className=\"absolute top-2 right-2 z-10 flex items-center gap-1 opacity-0 transition-opacity group-focus-within/code-block:opacity-100 group-hover/code-block:opacity-100\">\n {searchButton}\n {copyButton}\n {downloadButton}\n </div>\n )}\n <div\n className={cn('overflow-x-auto', maxHeight && 'overflow-y-auto')}\n style={maxHeight ? { maxHeight } : undefined}\n >\n <div\n ref={codeRef}\n className={cn(\n '[&_code]:font-mono [&_pre]:m-0 [&_pre]:p-4 group-data-[size=sm]/code-block:[&_pre]:p-3',\n html && '[&_pre]:bg-transparent!',\n html && SHIKI_DARK_THEME,\n lineNumbers && LINE_NUMBER_GUTTER,\n )}\n >\n {html ? (\n <div dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n <pre>\n <code className={lineNumbers ? undefined : 'whitespace-pre'}>\n {lineNumbers\n ? content.split('\\n').map((line, i) => (\n <span key={i} className=\"line\">\n {line}\n {'\\n'}\n </span>\n ))\n : content}\n </code>\n </pre>\n )}\n </div>\n </div>\n </div>\n </CardContent>\n );\n}\n\nfunction CodeBlockFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <CardFooter\n className={cn(\n 'text-body-sm group-data-[variant=terminal]/code-block:border-terminal-border group-data-[variant=terminal]/code-block:bg-terminal-footer-bg group-data-[variant=terminal]/code-block:text-terminal-muted',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CodeBlockCopyButton({\n code,\n className,\n ...props\n}: Omit<React.ComponentProps<'button'>, 'children'> & {\n code: string;\n}) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n data-slot=\"code-block-copy-button\"\n onClick={handleCopy}\n className={cn(CODE_BLOCK_ICON_BUTTON, className)}\n aria-label={copied ? 'Copied' : 'Copy code'}\n {...props}\n >\n {copied ? <Icon name=\"check\" size=\"sm\" className=\"text-green-500\" /> : <Icon name=\"copy\" size=\"sm\" />}\n </button>\n </TooltipTrigger>\n <TooltipContent>{copied ? 'Copied' : 'Copy'}</TooltipContent>\n </Tooltip>\n );\n}\n\nfunction CodeBlockDownloadButton({\n code,\n fileName,\n mimeType = 'text/plain',\n className,\n ...props\n}: Omit<React.ComponentProps<'button'>, 'children'> & {\n code: string;\n fileName: string;\n mimeType?: string;\n}) {\n const [downloaded, setDownloaded] = useState(false);\n\n const handleDownload = () => {\n const blob = new Blob([code], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n setDownloaded(true);\n setTimeout(() => setDownloaded(false), 2000);\n };\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n data-slot=\"code-block-download-button\"\n onClick={handleDownload}\n className={cn(CODE_BLOCK_ICON_BUTTON, className)}\n aria-label={downloaded ? 'Downloaded' : 'Download code'}\n {...props}\n >\n {downloaded ? <Icon name=\"check\" size=\"sm\" className=\"text-green-500\" /> : <Icon name=\"download\" size=\"sm\" />}\n </button>\n </TooltipTrigger>\n <TooltipContent>{downloaded ? 'Downloaded' : 'Download'}</TooltipContent>\n </Tooltip>\n );\n}\n\nexport { CodeBlock, CodeBlockHeader, CodeBlockBody, CodeBlockFooter, CodeBlockCopyButton, CodeBlockDownloadButton };\n","export { cn } from '@upbound/monarch-core/utils';\n\nexport function sortBy<T>(items: T[], getKey: (item: T) => string): T[] {\n return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));\n}\n","/**\n * Find every case-insensitive occurrence of `query` in the text under `root` and return one DOM\n * `Range` per match. Matches can cross element boundaries (Shiki wraps each token in its own\n * span), and don't overlap.\n */\nexport function findTextRanges(root: Node, query: string): Range[] {\n const needle = query.toLowerCase();\n if (!needle) return [];\n\n const doc = root.ownerDocument ?? document;\n const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n const nodes: Text[] = [];\n const starts: number[] = [];\n let text = '';\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n nodes.push(node as Text);\n starts.push(text.length);\n text += node.nodeValue ?? '';\n }\n\n // The text node holding `offset`. An end offset that falls exactly on a node boundary belongs\n // to the node before it, so a match never ends at the start of the next token.\n let nodeIndex = 0;\n const locate = (offset: number, isEnd: boolean): [Text, number] => {\n while (nodeIndex < nodes.length - 1 && (isEnd ? starts[nodeIndex + 1] < offset : starts[nodeIndex + 1] <= offset)) {\n nodeIndex++;\n }\n return [nodes[nodeIndex], offset - starts[nodeIndex]];\n };\n\n const haystack = text.toLowerCase();\n const ranges: Range[] = [];\n for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + needle.length)) {\n const range = doc.createRange();\n range.setStart(...locate(at, false));\n range.setEnd(...locate(at + needle.length, true));\n ranges.push(range);\n }\n return ranges;\n}\n","/**\n * Props for search and find fields, so browsers and password managers don't offer to fill them.\n *\n * - `type=\"search\"` is what Apple's autofill (Safari, iCloud Passwords) goes by; it ignores\n * `autoComplete=\"off\"` and treats a lone text field as a username.\n * - `autoComplete=\"off\"` covers browser autofill.\n * - The `data-*` attributes are the opt-outs 1Password, LastPass, Bitwarden, and Dashlane read.\n *\n * Pair with `SEARCH_INPUT_CLASS`, and give the field an `id` containing \"search\".\n */\nexport const SEARCH_INPUT_PROPS = {\n type: 'search',\n autoComplete: 'off',\n 'data-1p-ignore': true, // 1Password\n 'data-lpignore': 'true', // LastPass\n 'data-bwignore': true, // Bitwarden\n 'data-form-type': 'other', // Dashlane\n} as const;\n\n/** Hides the browser's own clear button on `type=\"search\"` fields. */\nexport const SEARCH_INPUT_CLASS = '[&::-webkit-search-cancel-button]:appearance-none';\n","import { createHighlighter, type Highlighter } from 'shiki';\n\nexport const CODE_BLOCK_LANGUAGES = ['tsx', 'typescript', 'bash', 'shell', 'json', 'css', 'yaml', 'python'] as const;\n\nexport type CodeBlockLanguage = (typeof CODE_BLOCK_LANGUAGES)[number];\n\nconst HIGHLIGHTER_KEY = '__monarch_shiki_highlighter__';\n\ntype HighlighterGlobal = typeof globalThis & {\n [HIGHLIGHTER_KEY]?: Promise<Highlighter>;\n};\n\n/**\n * Cache the in-flight Promise, not the resolved instance. `createHighlighter`\n * is async — storing only after `await` lets every concurrent CodeBlockBody\n * start a new highlighter (Shiki warns at 10/20/30 instances). `globalThis`\n * keeps the same promise across HMR module re-evals in the same realm.\n */\nfunction getHighlighter(): Promise<Highlighter> {\n const g = globalThis as HighlighterGlobal;\n if (!g[HIGHLIGHTER_KEY]) {\n g[HIGHLIGHTER_KEY] = createHighlighter({\n themes: ['github-light-high-contrast', 'github-dark-dimmed'],\n langs: [...CODE_BLOCK_LANGUAGES],\n }).catch(error => {\n delete g[HIGHLIGHTER_KEY];\n throw error;\n });\n }\n return g[HIGHLIGHTER_KEY];\n}\n\n/**\n * Highlight a code string with Shiki dual themes. Returns raw HTML safe to\n * render via dangerouslySetInnerHTML.\n *\n * Light theme renders as inline `color:` / `background-color:`.\n * Dark theme renders as CSS variables `--shiki-dark` / `--shiki-dark-bg`.\n */\nexport async function highlightCode(code: string, lang: CodeBlockLanguage): Promise<string> {\n const hl = await getHighlighter();\n return hl.codeToHtml(code, {\n lang,\n themes: {\n light: 'github-light-high-contrast',\n dark: 'github-dark-dimmed',\n },\n });\n}\n"],"mappings":"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","names":["_a","_b"]}
@@ -2,9 +2,9 @@ import * as React$1 from 'react';
2
2
  import { ReactNode, Dispatch, SetStateAction, ComponentType } from 'react';
3
3
  import { RowData, ColumnFilter, Cell, Column, ColumnOrderState, Row, ColumnDef, CellContext, HeaderGroup, TableOptions, TableState, InitialTableState, SortingFn, FilterFn, ExpandedState, ColumnFiltersState, PaginationState, SortingState, VisibilityState, ColumnPinningState, GroupingState, OnChangeFn, ColumnSizingState } from '@tanstack/react-table';
4
4
  import { DropdownMenuItem } from '@upbound/monarch-core';
5
- import { f as CelFilterSchema, g as CelFilterTree, k as CompileResult, i as CelRestrictedOptionSelectPayload } from './types-BULiU2qC.js';
6
- import { T as TableDataState, a as DataTableEmptyView } from './empty-view-CQtcr_Ew.js';
7
- export { D as DataTableEmptyConfig, d as defaultEmptyConfig, b as defaultErrorConfig, c as defaultNoFilterFoundConfig } from './empty-view-CQtcr_Ew.js';
5
+ import { CelFilterSchema, CelFilterTree, CompileResult, CelRestrictedOptionSelectPayload } from './cel-filter-bar.js';
6
+ import { T as TableDataState, a as DataTableEmptyView } from './empty-view-CjRwaITk.js';
7
+ export { D as DataTableEmptyConfig, d as defaultEmptyConfig, b as defaultErrorConfig, c as defaultNoFilterFoundConfig } from './empty-view-CjRwaITk.js';
8
8
 
9
9
  /** A single entry (or nested submenu) in a cell's contextual right-click menu, alongside the built-in "Filter by this value" item. */
10
10
  type ContextualMenuAction<TData extends RowData = RowData, TValue = unknown> = {
@@ -177,7 +177,7 @@ declare function DataTableBody<TData extends RowData>({ tableRows, tableState, c
177
177
  columns: Column<TData, unknown>[];
178
178
  skeletonRowCount?: number;
179
179
  renderSkeletonCell?: (columnId: string) => React$1.ReactNode | undefined;
180
- onRowRender?: (row: TData) => DataTableRowProps;
180
+ onRowRender?: (row: TData, tableRow: Row<TData>) => DataTableRowProps;
181
181
  renderExpandedRow?: (row: TData) => React$1.ReactNode;
182
182
  showContextualFilter?: boolean;
183
183
  onContextCell?: (filter: ColumnFilter, cell: Cell<TData, unknown>) => void;
@@ -222,6 +222,15 @@ type DataTableFilterVisibilityConfig = {
222
222
  /** Initial visibility when uncontrolled. @default false */
223
223
  defaultVisible?: boolean;
224
224
  };
225
+ /** Configures `DataTable`'s toolbar search box. */
226
+ type DataTableSearchConfig = {
227
+ /** Hides the search box and, together with `disableFilters`, turns off filtering. @default false */
228
+ disabled?: boolean;
229
+ /** Placeholder for the search input. @default 'Search…' */
230
+ placeholder?: string;
231
+ /** Additional className for the search box, e.g. to change its width (`w-70` by default). */
232
+ className?: string;
233
+ };
225
234
  /** Wires the CEL filter builder into the filter row in place of plain column filters. */
226
235
  type DataTableCelFilterConfig<TData extends RowData> = {
227
236
  schema: CelFilterSchema;
@@ -264,7 +273,13 @@ interface DataTableProps<TData extends RowData> {
264
273
  */
265
274
  fitContent?: boolean;
266
275
  serverSidePagination?: boolean;
267
- onRowRender?(row: TData): DataTableRowProps;
276
+ /** Extra props for each body row. `tableRow` is the TanStack row — use `tableRow.depth` to style `getSubRows` sub-rows. */
277
+ onRowRender?(row: TData, tableRow: Row<TData>): DataTableRowProps;
278
+ /**
279
+ * Renders full-width content under an expanded row. For expanded rows that line up with the
280
+ * table's columns, set `config.getSubRows` instead: sub-rows render through the same column
281
+ * `cell` functions, and expansion state works without this prop.
282
+ */
268
283
  renderExpandedRow?: (row: TData) => ReactNode;
269
284
  /** Controlled expanded state (optional). When omitted, `DataTable` uses internal state. */
270
285
  expanded?: ExpandedState;
@@ -272,7 +287,10 @@ interface DataTableProps<TData extends RowData> {
272
287
  ActionsComponent?: ComponentType<{
273
288
  state: TableDataState;
274
289
  }>;
290
+ /** @deprecated Use `search={{ disabled: true }}`. */
275
291
  disableSearch?: boolean;
292
+ /** Configures the toolbar search box. */
293
+ search?: DataTableSearchConfig;
276
294
  disableFilters?: boolean;
277
295
  disableSorting?: boolean;
278
296
  /**
@@ -350,7 +368,7 @@ interface DataTableProps<TData extends RowData> {
350
368
  * difference is that columns, headers, rows, cells, and menus render through
351
369
  * Monarch's own primitives instead of legacy's hand-rolled ones.
352
370
  */
353
- declare function DataTable<TData extends RowData>({ id, config, title, description, emptyView, showError, className, fitContent, loading, renderSkeletonCell, serverSidePagination, ActionsComponent: Actions, onRowRender, renderExpandedRow, expanded: parentExpanded, onExpandedChange: parentOnExpandedChange, disableSearch, disableFilters, disableSorting, paginationConfig, disableColumnReordering, disableColumnVisibility, disableColumnResizing, disableColumnPinning, searchTerm: parentGlobalFilter, onSearchTermChange: parentSetGlobalFilter, filters: parentColumnFilters, onFiltersChange: parentSetColumnFilters, pagination: parentPagination, onPaginationChange: parentSetPagination, sorting: parentSorting, onSortingChange: parentSetSorting, columnOrdering: parentColumnOrdering, onColumnOrderingChange: parentSetColumnOrdering, columnVisibility: parentColumnVisibility, onColumnVisibilityChange: parentSetColumnVisibility, columnPinning: parentColumnPinning, onColumnPinningChange: parentSetColumnPinning, grouping: parentGrouping, onGroupingChange: parentSetGrouping, celFilterClassName, showContextualFilter, onContextCell, contextualMenuActions, layout, initialColumnSizing, onColumnSizingPersist, stickyHeader, celFilterConfig, filterVisibility, }: DataTableProps<TData>): React$1.JSX.Element;
371
+ declare function DataTable<TData extends RowData>({ id, config, title, description, emptyView, showError, className, fitContent, loading, renderSkeletonCell, serverSidePagination, ActionsComponent: Actions, onRowRender, renderExpandedRow, expanded: parentExpanded, onExpandedChange: parentOnExpandedChange, disableSearch: deprecatedDisableSearch, search, disableFilters, disableSorting, paginationConfig, disableColumnReordering, disableColumnVisibility, disableColumnResizing, disableColumnPinning, searchTerm: parentGlobalFilter, onSearchTermChange: parentSetGlobalFilter, filters: parentColumnFilters, onFiltersChange: parentSetColumnFilters, pagination: parentPagination, onPaginationChange: parentSetPagination, sorting: parentSorting, onSortingChange: parentSetSorting, columnOrdering: parentColumnOrdering, onColumnOrderingChange: parentSetColumnOrdering, columnVisibility: parentColumnVisibility, onColumnVisibilityChange: parentSetColumnVisibility, columnPinning: parentColumnPinning, onColumnPinningChange: parentSetColumnPinning, grouping: parentGrouping, onGroupingChange: parentSetGrouping, celFilterClassName, showContextualFilter, onContextCell, contextualMenuActions, layout, initialColumnSizing, onColumnSizingPersist, stickyHeader, celFilterConfig, filterVisibility, }: DataTableProps<TData>): React$1.JSX.Element;
354
372
 
355
373
  type ViewFilterProps<TData extends RowData> = {
356
374
  columns: Column<TData, unknown>[];
@@ -370,4 +388,5 @@ type ViewFilterProps<TData extends RowData> = {
370
388
  */
371
389
  declare function ViewFilter<TData extends RowData>({ columns, columnFilters, onColumnFiltersChange, className, }: ViewFilterProps<TData>): React$1.JSX.Element;
372
390
 
373
- export { CellContextualFilter, type CellContextualFilterProps, ColumnOptions, type ColumnOptionsProps, type ContextualMenuAction, DataTable, DataTableBody, type DataTableCelFilterConfig, DataTableCell, DataTableEmptyView, DataTableExpandedRow, type DataTableFilterVisibilityConfig, DataTableGroupRow, DataTableHeader, DataTablePagination, type DataTablePaginationConfig, type DataTablePaginationProps, type DataTableProps, DataTableRow, type DataTableRowProps, DataTableSkeletonBody, DataTableValueCell, type DataTableValueCellProps, ExpandButton, RowActionItem, RowActions, type RowCountRelation, type StickyHeaderConfig, type TableHookOptions, type TableLayout, TimestampCell, ViewFilter, type ViewFilterProps, getActionsColumnDefinition, getExpandColumnDefinition, normalizePageSizeOptions };
391
+ export { CellContextualFilter, ColumnOptions, DataTable, DataTableBody, DataTableCell, DataTableEmptyView, DataTableExpandedRow, DataTableGroupRow, DataTableHeader, DataTablePagination, DataTableRow, DataTableSkeletonBody, DataTableValueCell, ExpandButton, RowActionItem, RowActions, TimestampCell, ViewFilter, getActionsColumnDefinition, getExpandColumnDefinition, normalizePageSizeOptions };
392
+ export type { CellContextualFilterProps, ColumnOptionsProps, ContextualMenuAction, DataTableCelFilterConfig, DataTableFilterVisibilityConfig, DataTablePaginationConfig, DataTablePaginationProps, DataTableProps, DataTableRowProps, DataTableSearchConfig, DataTableValueCellProps, RowCountRelation, StickyHeaderConfig, TableHookOptions, TableLayout, ViewFilterProps };