@upbound/monarch-blocks 0.6.0 → 0.7.1

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.
@@ -32,41 +32,11 @@ var __objRest = (source, exclude) => {
32
32
  };
33
33
 
34
34
  // src/code-block.tsx
35
- import { useState } from "react";
35
+ import { useEffect, useState } from "react";
36
36
  import { Icon } from "@upbound/monarch-core";
37
37
 
38
38
  // src/lib/utils.ts
39
- import { clsx } from "clsx";
40
- import { extendTailwindMerge } from "tailwind-merge";
41
- var twMerge = extendTailwindMerge({
42
- extend: {
43
- classGroups: {
44
- "font-size": [
45
- {
46
- text: [
47
- "display-hero",
48
- "display-kpi-sm",
49
- "display-kpi",
50
- "display-kpi-lg",
51
- "display-feature",
52
- "h1",
53
- "h2",
54
- "h3",
55
- "h4",
56
- "body-lg",
57
- "body",
58
- "body-sm",
59
- "caption",
60
- "eyebrow"
61
- ]
62
- }
63
- ]
64
- }
65
- }
66
- });
67
- function cn(...inputs) {
68
- return twMerge(clsx(inputs));
69
- }
39
+ import { cn } from "@upbound/monarch-core/utils";
70
40
 
71
41
  // src/code-block.tsx
72
42
  import {
@@ -78,6 +48,36 @@ import {
78
48
  TooltipContent,
79
49
  TooltipTrigger
80
50
  } from "@upbound/monarch-core";
51
+
52
+ // src/lib/highlight.ts
53
+ import { createHighlighter } from "shiki";
54
+ var CODE_BLOCK_LANGUAGES = ["tsx", "typescript", "bash", "shell", "json", "css", "yaml"];
55
+ var HIGHLIGHTER_KEY = "__monarch_shiki_highlighter__";
56
+ function getHighlighter() {
57
+ const g = globalThis;
58
+ if (!g[HIGHLIGHTER_KEY]) {
59
+ g[HIGHLIGHTER_KEY] = createHighlighter({
60
+ themes: ["github-light-high-contrast", "github-dark-dimmed"],
61
+ langs: [...CODE_BLOCK_LANGUAGES]
62
+ }).catch((error) => {
63
+ delete g[HIGHLIGHTER_KEY];
64
+ throw error;
65
+ });
66
+ }
67
+ return g[HIGHLIGHTER_KEY];
68
+ }
69
+ async function highlightCode(code, lang) {
70
+ const hl = await getHighlighter();
71
+ return hl.codeToHtml(code, {
72
+ lang,
73
+ themes: {
74
+ light: "github-light-high-contrast",
75
+ dark: "github-dark-dimmed"
76
+ }
77
+ });
78
+ }
79
+
80
+ // src/code-block.tsx
81
81
  import { jsx, jsxs } from "react/jsx-runtime";
82
82
  function CodeBlock(_a) {
83
83
  var _b = _a, {
@@ -118,24 +118,48 @@ function CodeBlockHeader(_a) {
118
118
  }, props)
119
119
  );
120
120
  }
121
+ var LINE_NUMBER_GUTTER = cn(
122
+ "[&_code]:[counter-reset:line]",
123
+ "[&_.line]:before:[counter-increment:line]",
124
+ "[&_.line]:before:content-[counter(line)]",
125
+ "[&_.line]:before:inline-block",
126
+ "[&_.line]:before:w-6",
127
+ "[&_.line]:before:mr-3",
128
+ "[&_.line]:before:text-right",
129
+ "[&_.line]:before:text-muted-foreground",
130
+ "[&_.line]:before:text-[0.75rem]",
131
+ "[&_.line]:before:select-none"
132
+ );
121
133
  function CodeBlockBody(_a) {
122
134
  var _b = _a, {
123
135
  className,
124
136
  code,
125
- html,
126
- lineNumbers,
127
137
  maxHeight,
128
138
  copyButton,
129
139
  downloadButton
130
140
  } = _b, props = __objRest(_b, [
131
141
  "className",
132
142
  "code",
133
- "html",
134
- "lineNumbers",
135
143
  "maxHeight",
136
144
  "copyButton",
137
145
  "downloadButton"
138
146
  ]);
147
+ const { content, lineNumbers } = code;
148
+ const [html, setHtml] = useState(null);
149
+ const language = code.type === "parsed" ? code.language : null;
150
+ useEffect(() => {
151
+ if (!language) {
152
+ setHtml(null);
153
+ return;
154
+ }
155
+ let cancelled = false;
156
+ highlightCode(content, language).then((result) => {
157
+ if (!cancelled) setHtml(result);
158
+ });
159
+ return () => {
160
+ cancelled = true;
161
+ };
162
+ }, [content, language]);
139
163
  return /* @__PURE__ */ jsxs(CardContent, __spreadProps(__spreadValues({ "data-line-numbers": lineNumbers || void 0, className: cn("relative p-0!", className) }, props), { children: [
140
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: [
141
165
  copyButton,
@@ -146,16 +170,20 @@ function CodeBlockBody(_a) {
146
170
  {
147
171
  className: cn("overflow-x-auto", maxHeight && "overflow-y-auto"),
148
172
  style: maxHeight ? { maxHeight } : void 0,
149
- children: html ? /* @__PURE__ */ jsx(
173
+ children: /* @__PURE__ */ jsx(
150
174
  "div",
151
175
  {
152
- className: "[&_code]:font-mono [&_pre]:m-0 [&_pre]:bg-transparent! [&_pre]:p-4 group-data-[size=sm]/code-block:[&_pre]:p-3",
153
- dangerouslySetInnerHTML: { __html: html }
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 }) })
154
185
  }
155
- ) : lineNumbers ? /* @__PURE__ */ jsx("pre", { className: "p-4 group-data-[size=sm]/code-block:p-3", children: /* @__PURE__ */ jsx("code", { className: "font-mono", children: code.split("\n").map((line, i) => /* @__PURE__ */ jsxs("span", { className: "line", children: [
156
- line,
157
- "\n"
158
- ] }, i)) }) }) : /* @__PURE__ */ jsx("pre", { className: "p-4 group-data-[size=sm]/code-block:p-3", children: /* @__PURE__ */ jsx("code", { className: "font-mono whitespace-pre", children: code }) })
186
+ )
159
187
  }
160
188
  )
161
189
  ] }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/code-block.tsx","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { 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';\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\nfunction CodeBlockBody({\n className,\n code,\n html,\n lineNumbers,\n maxHeight,\n copyButton,\n downloadButton,\n ...props\n}: React.ComponentProps<'div'> & {\n code: string;\n html?: string;\n lineNumbers?: boolean;\n maxHeight?: string;\n copyButton?: React.ReactNode;\n downloadButton?: React.ReactNode;\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 {html ? (\n <div\n className=\"[&_code]:font-mono [&_pre]:m-0 [&_pre]:bg-transparent! [&_pre]:p-4 group-data-[size=sm]/code-block:[&_pre]:p-3\"\n dangerouslySetInnerHTML={{ __html: html }}\n />\n ) : lineNumbers ? (\n <pre className=\"p-4 group-data-[size=sm]/code-block:p-3\">\n <code className=\"font-mono\">\n {code.split('\\n').map((line, i) => (\n <span key={i} className=\"line\">\n {line}\n {'\\n'}\n </span>\n ))}\n </code>\n </pre>\n ) : (\n <pre className=\"p-4 group-data-[size=sm]/code-block:p-3\">\n <code className=\"font-mono whitespace-pre\">{code}</code>\n </pre>\n )}\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","import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n {\n text: [\n 'display-hero',\n 'display-kpi-sm',\n 'display-kpi',\n 'display-kpi-lg',\n 'display-feature',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'body-lg',\n 'body',\n 'body-sm',\n 'caption',\n 'eyebrow',\n ],\n },\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\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"],"mappings":"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","names":[]}
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":[]}
@@ -256,7 +256,12 @@ interface DataTableProps<TData extends RowData> {
256
256
  className?: string;
257
257
  /** Table layout mode. 'auto' uses native HTML table layout, 'grid' uses CSS Grid with div elements. @default 'auto' */
258
258
  layout?: TableLayout;
259
- /** When true, table height fits content instead of using viewport-based height constraints. @default false */
259
+ /**
260
+ * When true, table height fits content instead of using viewport-based height constraints.
261
+ * The row scroll area then grows with the rows, so `stickyHeader` has no effect — a sticky
262
+ * header needs a scroll area that is shorter than its content to stick against.
263
+ * @default false
264
+ */
260
265
  fitContent?: boolean;
261
266
  serverSidePagination?: boolean;
262
267
  onRowRender?(row: TData): DataTableRowProps;
@@ -312,6 +317,10 @@ interface DataTableProps<TData extends RowData> {
312
317
  /**
313
318
  * Enable sticky column headers within the table scroll area.
314
319
  * Pass `true` for defaults, or an object to customise offset / row height.
320
+ *
321
+ * This caps the scroll area at a viewport-relative height so the rows scroll under the
322
+ * header. `fitContent` removes that cap, so the two props do not work together: with
323
+ * `fitContent` the table grows to its content and the header does not stick.
315
324
  */
316
325
  stickyHeader?: StickyHeaderConfig;
317
326
  /** When set, renders the CEL filter builder in the filter row instead of column filters. */
@@ -129,37 +129,7 @@ import {
129
129
  import { Icon } from "@upbound/monarch-core";
130
130
 
131
131
  // src/lib/utils.ts
132
- import { clsx } from "clsx";
133
- import { extendTailwindMerge } from "tailwind-merge";
134
- var twMerge = extendTailwindMerge({
135
- extend: {
136
- classGroups: {
137
- "font-size": [
138
- {
139
- text: [
140
- "display-hero",
141
- "display-kpi-sm",
142
- "display-kpi",
143
- "display-kpi-lg",
144
- "display-feature",
145
- "h1",
146
- "h2",
147
- "h3",
148
- "h4",
149
- "body-lg",
150
- "body",
151
- "body-sm",
152
- "caption",
153
- "eyebrow"
154
- ]
155
- }
156
- ]
157
- }
158
- }
159
- });
160
- function cn(...inputs) {
161
- return twMerge(clsx(inputs));
162
- }
132
+ import { cn } from "@upbound/monarch-core/utils";
163
133
  function sortBy(items, getKey) {
164
134
  return [...items].sort((a, b) => getKey(a).localeCompare(getKey(b)));
165
135
  }
@@ -167,10 +137,9 @@ function sortBy(items, getKey) {
167
137
  // src/lib/data-table/utils.ts
168
138
  var TO_EACH_SIDE = 2;
169
139
  function normalizeColumnOrdering(columnOrdering, allColumnIds) {
170
- const validIds = new Set(allColumnIds);
171
140
  const seenIds = /* @__PURE__ */ new Set();
172
141
  const trackedColumnIds = columnOrdering.filter((columnId) => {
173
- if (!validIds.has(columnId) || seenIds.has(columnId)) return false;
142
+ if (seenIds.has(columnId)) return false;
174
143
  seenIds.add(columnId);
175
144
  return true;
176
145
  });
@@ -1829,7 +1798,7 @@ function CelAddFilterButton({
1829
1798
  if (column) onSelect(column.id);
1830
1799
  },
1831
1800
  children: [
1832
- /* @__PURE__ */ jsxs4(ComboboxTrigger, { "aria-label": "Add filter", render: /* @__PURE__ */ jsx5(Button3, { variant: "outline" }), children: [
1801
+ /* @__PURE__ */ jsxs4(ComboboxTrigger, { "aria-label": "Add filter", showIndicator: false, render: /* @__PURE__ */ jsx5(Button3, { variant: "outline" }), children: [
1833
1802
  /* @__PURE__ */ jsx5(Icon3, { name: "plus", "data-icon": "inline-start" }),
1834
1803
  "Add filter"
1835
1804
  ] }),
@@ -3296,9 +3265,9 @@ function DataTableSortIcon({ sortOrder }) {
3296
3265
  return /* @__PURE__ */ jsx9(
3297
3266
  Icon7,
3298
3267
  {
3299
- name: sortOrder === "asc" ? "arrow-up" : "arrow-down",
3300
3268
  size: "xs",
3301
- className: "text-primary group-hover/col-actions:text-foreground group-data-[state=open]/col-actions:text-foreground mr-0.5 shrink-0"
3269
+ name: sortOrder === "asc" ? "arrow-up" : "arrow-down",
3270
+ className: "text-foreground group-hover/col-actions:text-foreground group-data-[state=open]/col-actions:text-foreground mr-0.5 shrink-0"
3302
3271
  }
3303
3272
  );
3304
3273
  }
@@ -3390,7 +3359,7 @@ function HeaderCellContent({
3390
3359
  "aria-label": shouldShowActionsMenu ? headerTitle ? `${headerTitle} column actions` : "Column actions" : void 0,
3391
3360
  className: cn(
3392
3361
  "group/col-actions text-muted-foreground -mx-2 -my-1 flex min-w-0 items-center gap-1 truncate rounded-md px-2 py-1 text-left font-medium motion-safe:transition-colors",
3393
- sorted && "text-primary",
3362
+ sorted && "text-foreground",
3394
3363
  shouldShowActionsMenu ? "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50 cursor-pointer" : "cursor-default"
3395
3364
  ),
3396
3365
  children: [
@@ -3878,7 +3847,7 @@ function FilterBar(_a) {
3878
3847
  if (filter) handleAddFilter(filter.key);
3879
3848
  },
3880
3849
  children: [
3881
- /* @__PURE__ */ jsxs9(ComboboxTrigger2, { "aria-label": "Add filter", render: /* @__PURE__ */ jsx10(Button6, { variant: "outline" }), children: [
3850
+ /* @__PURE__ */ jsxs9(ComboboxTrigger2, { "aria-label": "Add filter", showIndicator: false, render: /* @__PURE__ */ jsx10(Button6, { variant: "outline" }), children: [
3882
3851
  /* @__PURE__ */ jsx10(Icon8, { name: "plus", "data-icon": "inline-start" }),
3883
3852
  "Add filter"
3884
3853
  ] }),
@@ -4434,6 +4403,7 @@ function SectionHeaderDescription(_a) {
4434
4403
  // src/data-table/data-table.tsx
4435
4404
  import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
4436
4405
  var DEFAULT_PAGE_SIZE = 20;
4406
+ var STICKY_HEADER_MAX_HEIGHT_CLASS = "max-h-[calc(100svh-250px)]";
4437
4407
  function DataTable({
4438
4408
  id,
4439
4409
  config,
@@ -4771,9 +4741,10 @@ function DataTable({
4771
4741
  "div",
4772
4742
  {
4773
4743
  ref: tableBodyScrollRef,
4744
+ "data-testid": "table-scroll-container",
4774
4745
  className: cn(
4775
4746
  "min-h-0 flex-1 overflow-auto rounded-lg border **:data-[slot=table-container]:overflow-visible",
4776
- !!stickyHeader && "max-h-[calc(100svh-250px)]"
4747
+ !!stickyHeader && !fitContent && STICKY_HEADER_MAX_HEIGHT_CLASS
4777
4748
  ),
4778
4749
  children: /* @__PURE__ */ jsxs10(
4779
4750
  DataTableRoot,