@tangle-network/ui 11.35.0 → 11.37.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 (93) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist/code-presentation.d.ts +2 -0
  4. package/dist/code-presentation.js +2 -0
  5. package/dist/index.d.ts +8 -5
  6. package/dist/index.js +6 -3
  7. package/dist/lib/code-presentation.d.ts +92 -0
  8. package/dist/lib/code-presentation.js +437 -0
  9. package/dist/lib/control-presentation.d.ts +34 -1
  10. package/dist/lib/control-presentation.js +1 -1
  11. package/dist/lib/focus.d.ts +2 -2
  12. package/dist/lib/focus.js +3 -2
  13. package/dist/lib/motion.d.ts +4 -0
  14. package/dist/lib/motion.js +7 -0
  15. package/dist/lib/surface-motion.js +105 -0
  16. package/dist/markdown/api-schema.d.ts +9 -0
  17. package/dist/markdown/api-schema.js +20 -0
  18. package/dist/markdown/code-block.d.ts +15 -7
  19. package/dist/markdown/code-block.js +120 -268
  20. package/dist/markdown.d.ts +4 -2
  21. package/dist/markdown.js +4 -2
  22. package/dist/presentation.d.ts +88 -0
  23. package/dist/presentation.js +183 -0
  24. package/dist/primitives/alert-dialog.js +1 -1
  25. package/dist/primitives/alert.d.ts +1 -2
  26. package/dist/primitives/button.d.ts +2 -5
  27. package/dist/primitives/button.js +5 -31
  28. package/dist/primitives/card.js +3 -7
  29. package/dist/primitives/code-block.d.ts +1 -1
  30. package/dist/primitives/date-picker.d.ts +1 -1
  31. package/dist/primitives/dialog.js +29 -16
  32. package/dist/primitives/empty-state.js +6 -5
  33. package/dist/primitives/heading.d.ts +1 -0
  34. package/dist/primitives/heading.js +3 -2
  35. package/dist/primitives/help-text.js +2 -1
  36. package/dist/primitives/input.d.ts +1 -4
  37. package/dist/primitives/input.js +3 -24
  38. package/dist/primitives/label.js +1 -2
  39. package/dist/primitives/metric-strip.js +7 -12
  40. package/dist/primitives/page-header.d.ts +1 -0
  41. package/dist/primitives/page-header.js +7 -6
  42. package/dist/primitives/page-shell.d.ts +8 -2
  43. package/dist/primitives/page-shell.js +21 -2
  44. package/dist/primitives/pagination.js +1 -1
  45. package/dist/primitives/popover.js +11 -3
  46. package/dist/primitives/sheet.js +29 -19
  47. package/dist/primitives/skeleton.js +1 -1
  48. package/dist/primitives/status-pill.d.ts +3 -1
  49. package/dist/primitives/status-pill.js +12 -3
  50. package/dist/primitives/table.js +10 -9
  51. package/dist/primitives/tabs.js +3 -12
  52. package/dist/primitives/tooltip.js +11 -3
  53. package/dist/primitives.d.ts +3 -2
  54. package/dist/primitives.js +2 -1
  55. package/dist/utils.d.ts +1 -1
  56. package/package.json +20 -5
  57. package/src/code-presentation.ts +2 -0
  58. package/src/lib/code-presentation.ts +257 -0
  59. package/src/lib/control-presentation.ts +2 -4
  60. package/src/lib/focus.ts +3 -2
  61. package/src/lib/motion.ts +7 -0
  62. package/src/lib/surface-motion.tsx +85 -0
  63. package/src/markdown/api-presentation.test.tsx +63 -0
  64. package/src/markdown/api-schema.tsx +9 -0
  65. package/src/markdown/code-block.tsx +54 -185
  66. package/src/markdown/index.ts +2 -1
  67. package/src/presentation.ts +205 -0
  68. package/src/primitives/alert.tsx +1 -2
  69. package/src/primitives/button.tsx +6 -41
  70. package/src/primitives/card.tsx +5 -10
  71. package/src/primitives/checkbox-separator-sheet-tooltip.test.tsx +2 -1
  72. package/src/primitives/code-block.tsx +1 -1
  73. package/src/primitives/control-presentation.test.tsx +2 -7
  74. package/src/primitives/dialog.tsx +19 -18
  75. package/src/primitives/empty-state.tsx +6 -5
  76. package/src/primitives/heading.tsx +4 -2
  77. package/src/primitives/help-text.tsx +3 -2
  78. package/src/primitives/input.tsx +3 -31
  79. package/src/primitives/label.tsx +2 -4
  80. package/src/primitives/metric-strip.tsx +8 -17
  81. package/src/primitives/page-header.tsx +8 -5
  82. package/src/primitives/page-shell.tsx +13 -2
  83. package/src/primitives/popover.tsx +6 -8
  84. package/src/primitives/sheet.tsx +16 -11
  85. package/src/primitives/skeleton.tsx +1 -1
  86. package/src/primitives/status-pill.test.tsx +1 -0
  87. package/src/primitives/status-pill.tsx +10 -4
  88. package/src/primitives/table.tsx +10 -9
  89. package/src/primitives/tabs.tsx +5 -17
  90. package/src/primitives/tooltip.tsx +6 -5
  91. package/src/styles/compat.css +12 -0
  92. package/src/styles/presentation.css +38 -0
  93. package/src/primitives/page-shell.test.tsx +0 -24
@@ -0,0 +1,20 @@
1
+ import { schemaRows } from "../lib/code-presentation.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/markdown/api-schema.tsx
4
+ /** Presentation only: the consuming API owns all fields and constraints. */
5
+ function ApiSchema({ fields, label = "Schema" }) {
6
+ return /* @__PURE__ */ jsx("dl", {
7
+ "aria-label": label,
8
+ className: "tangle-api-schema",
9
+ children: schemaRows(fields).map((field, index) => /* @__PURE__ */ jsxs("div", {
10
+ style: { paddingInlineStart: `${field.depth}rem` },
11
+ children: [/* @__PURE__ */ jsxs("dt", { children: [
12
+ /* @__PURE__ */ jsx("code", { children: field.path }),
13
+ " ",
14
+ /* @__PURE__ */ jsxs("span", { children: [field.type, field.required ? " · required" : ""] })
15
+ ] }), field.description && /* @__PURE__ */ jsx("dd", { children: field.description })]
16
+ }, `${field.path}-${index}`))
17
+ });
18
+ }
19
+ //#endregion
20
+ export { ApiSchema };
@@ -1,8 +1,10 @@
1
- import { HTMLAttributes, ReactNode } from "react";
1
+ import { ApiMetadata, CodeExample, codeBlockGrammar } from "../lib/code-presentation.js";
2
+ import { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
2
3
  //#region src/markdown/code-block.d.ts
3
- export declare function codeBlockGrammar(language: string | undefined): string | undefined;
4
4
  export interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {
5
5
  code: string;
6
+ examples?: readonly CodeExample[];
7
+ metadata?: ApiMetadata;
6
8
  language?: string;
7
9
  /**
8
10
  * Header text. Defaults to the language's name. Set this when the display
@@ -20,9 +22,15 @@ export interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {
20
22
  * Code with Shiki highlighting in Brand's syntax colors. It renders readable
21
23
  * plain code first (and on the server), then highlights once its grammar loads.
22
24
  */
23
- export declare const CodeBlock: import("react").MemoExoticComponent<({ code, language, label, filename, showLineNumbers, light, className, children, ...props }: CodeBlockProps) => import("react").JSX.Element>;
24
- /** Copy-to-clipboard button for use inside CodeBlock. */
25
- export declare const CopyButton: import("react").MemoExoticComponent<({ text }: {
25
+ export declare const CodeBlock: import("react").MemoExoticComponent<({ code: defaultCode, language: defaultLanguage, label, filename: defaultFilename, examples, metadata, showLineNumbers, light, className, children, ...props }: CodeBlockProps) => import("react").JSX.Element>;
26
+ export interface CopyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
26
27
  text: string;
27
- }) => import("react").JSX.Element>;
28
- //#endregion
28
+ /** Supply for a visible action label; omitted preserves the icon-only control. */
29
+ label?: string;
30
+ copiedLabel?: string;
31
+ failedLabel?: string;
32
+ }
33
+ /** Copy-to-clipboard button for use inside CodeBlock or other exact-text actions. */
34
+ export declare const CopyButton: import("react").MemoExoticComponent<({ text, label: actionLabel, copiedLabel, failedLabel, className, onClick, ...props }: CopyButtonProps) => import("react").JSX.Element>;
35
+ //#endregion
36
+ export { codeBlockGrammar };
@@ -1,186 +1,23 @@
1
1
  import { focusRing, focusRingInset } from "../lib/focus.js";
2
2
  import { cn } from "../lib/utils.js";
3
- import { memo, useCallback, useEffect, useRef, useState } from "react";
3
+ import { LANGUAGE_NAMES, PLAIN_TEXT, SYNTAX, apiMetadataText, bindCodeTabs, codeBlockGrammar, codePreStyle, codePresentation, codeTextStyle, createCopyFeedback, tokenStyle, tokenize } from "../lib/code-presentation.js";
4
+ import { memo, useEffect, useId, useRef, useState } from "react";
4
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
6
  import { Check, Copy, FileCode2 } from "lucide-react";
6
7
  //#region src/markdown/code-block.tsx
7
- const SYNTAX = {
8
- comment: "var(--syntax-comment, currentColor)",
9
- keyword: "var(--syntax-keyword, currentColor)",
10
- string: "var(--syntax-string, currentColor)",
11
- function: "var(--syntax-function, currentColor)",
12
- number: "var(--syntax-number, currentColor)",
13
- variable: "var(--syntax-variable, currentColor)",
14
- foreground: "var(--syntax-foreground, currentColor)"
15
- };
16
- const SHIKI_THEME = "tangle-syntax";
17
- const BRAND_COLOR_FOR_SHIKI = {
18
- "var(--shiki-foreground)": SYNTAX.foreground,
19
- "var(--shiki-token-comment)": SYNTAX.comment,
20
- "var(--shiki-token-keyword)": SYNTAX.keyword,
21
- "var(--shiki-token-string)": SYNTAX.string,
22
- "var(--shiki-token-string-expression)": SYNTAX.string,
23
- "var(--shiki-token-function)": SYNTAX.function,
24
- "var(--shiki-token-constant)": SYNTAX.number,
25
- "var(--shiki-token-link)": SYNTAX.number,
26
- "var(--shiki-token-parameter)": SYNTAX.variable,
27
- "var(--shiki-token-punctuation)": SYNTAX.foreground
28
- };
29
- const GRAMMARS = {
30
- typescript: () => import("shiki/langs/typescript.mjs"),
31
- tsx: () => import("shiki/langs/tsx.mjs"),
32
- javascript: () => import("shiki/langs/javascript.mjs"),
33
- jsx: () => import("shiki/langs/jsx.mjs"),
34
- json: () => import("shiki/langs/json.mjs"),
35
- jsonc: () => import("shiki/langs/jsonc.mjs"),
36
- bash: () => import("shiki/langs/bash.mjs"),
37
- python: () => import("shiki/langs/python.mjs"),
38
- html: () => import("shiki/langs/html.mjs"),
39
- css: () => import("shiki/langs/css.mjs"),
40
- scss: () => import("shiki/langs/scss.mjs"),
41
- markdown: () => import("shiki/langs/markdown.mjs"),
42
- mdx: () => import("shiki/langs/mdx.mjs"),
43
- yaml: () => import("shiki/langs/yaml.mjs"),
44
- toml: () => import("shiki/langs/toml.mjs"),
45
- sql: () => import("shiki/langs/sql.mjs"),
46
- diff: () => import("shiki/langs/diff.mjs"),
47
- go: () => import("shiki/langs/go.mjs"),
48
- rust: () => import("shiki/langs/rust.mjs"),
49
- solidity: () => import("shiki/langs/solidity.mjs"),
50
- ruby: () => import("shiki/langs/ruby.mjs"),
51
- java: () => import("shiki/langs/java.mjs"),
52
- kotlin: () => import("shiki/langs/kotlin.mjs"),
53
- swift: () => import("shiki/langs/swift.mjs"),
54
- php: () => import("shiki/langs/php.mjs"),
55
- c: () => import("shiki/langs/c.mjs"),
56
- cpp: () => import("shiki/langs/cpp.mjs"),
57
- csharp: () => import("shiki/langs/csharp.mjs"),
58
- graphql: () => import("shiki/langs/graphql.mjs"),
59
- xml: () => import("shiki/langs/xml.mjs"),
60
- dockerfile: () => import("shiki/langs/dockerfile.mjs")
61
- };
62
- const LANGUAGE_ALIASES = {
63
- ts: "typescript",
64
- mts: "typescript",
65
- cts: "typescript",
66
- js: "javascript",
67
- mjs: "javascript",
68
- cjs: "javascript",
69
- node: "javascript",
70
- sh: "bash",
71
- shell: "bash",
72
- zsh: "bash",
73
- console: "bash",
74
- shellscript: "bash",
75
- py: "python",
76
- python3: "python",
77
- yml: "yaml",
78
- md: "markdown",
79
- rs: "rust",
80
- sol: "solidity",
81
- rb: "ruby",
82
- kt: "kotlin",
83
- cs: "csharp",
84
- "c#": "csharp",
85
- "c++": "cpp",
86
- gql: "graphql",
87
- svg: "xml",
88
- docker: "dockerfile",
89
- patch: "diff",
90
- htm: "html"
91
- };
92
- const LANGUAGE_NAMES = {
93
- typescript: "TypeScript",
94
- tsx: "TSX",
95
- javascript: "JavaScript",
96
- jsx: "JSX",
97
- json: "JSON",
98
- jsonc: "JSONC",
99
- bash: "Bash",
100
- python: "Python",
101
- html: "HTML",
102
- css: "CSS",
103
- scss: "SCSS",
104
- markdown: "Markdown",
105
- mdx: "MDX",
106
- yaml: "YAML",
107
- toml: "TOML",
108
- sql: "SQL",
109
- diff: "Diff",
110
- go: "Go",
111
- rust: "Rust",
112
- solidity: "Solidity",
113
- ruby: "Ruby",
114
- java: "Java",
115
- kotlin: "Kotlin",
116
- swift: "Swift",
117
- php: "PHP",
118
- c: "C",
119
- cpp: "C++",
120
- csharp: "C#",
121
- graphql: "GraphQL",
122
- xml: "XML",
123
- dockerfile: "Dockerfile"
124
- };
125
- /** The Shiki grammar for a fence language, or undefined for plain text. */
126
- const PLAIN_TEXT = /* @__PURE__ */ new Set([
127
- "text",
128
- "txt",
129
- "plain",
130
- "plaintext"
131
- ]);
132
- function codeBlockGrammar(language) {
133
- if (!language) return void 0;
134
- const id = language.toLowerCase();
135
- const resolved = LANGUAGE_ALIASES[id] ?? id;
136
- return GRAMMARS[resolved] ? resolved : void 0;
137
- }
138
- let highlighterPromise;
139
- const grammarLoads = /* @__PURE__ */ new Map();
140
- function loadHighlighter() {
141
- return highlighterPromise ??= Promise.all([import("shiki/core"), import("shiki/engine/javascript")]).then(([core, engine]) => core.createHighlighterCore({
142
- themes: [core.createCssVariablesTheme({
143
- name: SHIKI_THEME,
144
- variablePrefix: "--shiki-",
145
- fontStyle: true
146
- })],
147
- langs: [],
148
- engine: engine.createJavaScriptRegexEngine()
149
- })).catch((error) => {
150
- highlighterPromise = void 0;
151
- throw error;
152
- });
153
- }
154
- async function tokenize(code, grammar) {
155
- const highlighter = await loadHighlighter();
156
- let load = grammarLoads.get(grammar);
157
- if (!load) {
158
- load = GRAMMARS[grammar]().then((module) => highlighter.loadLanguage(module.default));
159
- load.catch(() => grammarLoads.delete(grammar));
160
- grammarLoads.set(grammar, load);
161
- }
162
- await load;
163
- return highlighter.codeToTokensBase(code, {
164
- lang: grammar,
165
- theme: SHIKI_THEME
166
- });
167
- }
168
- function tokenStyle(token) {
169
- const color = token.color ? BRAND_COLOR_FOR_SHIKI[token.color] ?? SYNTAX.foreground : void 0;
170
- const style = token.fontStyle ?? 0;
171
- if (!color && !style) return void 0;
172
- return {
173
- ...color ? { color } : {},
174
- ...style & 1 ? { fontStyle: "italic" } : {},
175
- ...style & 2 ? { fontWeight: "bold" } : {},
176
- ...style & 4 ? { textDecoration: "underline" } : {}
177
- };
178
- }
179
8
  /**
180
9
  * Code with Shiki highlighting in Brand's syntax colors. It renders readable
181
10
  * plain code first (and on the server), then highlights once its grammar loads.
182
11
  */
183
- const CodeBlock = memo(({ code, language, label, filename, showLineNumbers = false, light, className, children, ...props }) => {
12
+ const CodeBlock = memo(({ code: defaultCode, language: defaultLanguage, label, filename: defaultFilename, examples, metadata, showLineNumbers = false, light, className, children, ...props }) => {
13
+ const [selected, setSelected] = useState(0);
14
+ const tabId = useId(), tabList = useRef(null);
15
+ const selectedIndex = Math.min(selected, Math.max(0, (examples?.length ?? 1) - 1));
16
+ const example = examples?.[selectedIndex];
17
+ const code = example?.code ?? defaultCode, language = example?.language ?? defaultLanguage, filename = example?.filename ?? defaultFilename;
18
+ useEffect(() => {
19
+ if (tabList.current) return bindCodeTabs(tabList.current, setSelected);
20
+ }, [examples?.length]);
184
21
  const grammar = codeBlockGrammar(language);
185
22
  const [highlighted, setHighlighted] = useState(null);
186
23
  useEffect(() => {
@@ -199,21 +36,8 @@ const CodeBlock = memo(({ code, language, label, filename, showLineNumbers = fal
199
36
  }, [code, grammar]);
200
37
  const languageName = grammar ? LANGUAGE_NAMES[grammar] : PLAIN_TEXT.has(language?.toLowerCase() ?? "") ? void 0 : language;
201
38
  const headerLabel = label ?? (filename ? void 0 : languageName);
202
- const hasHeader = Boolean(filename || headerLabel);
39
+ const hasHeader = Boolean(filename || headerLabel || metadata || examples?.length);
203
40
  const mode = light === void 0 ? {} : { "data-theme": light ? "light" : "dark" };
204
- const preStyle = {
205
- margin: 0,
206
- padding: "var(--code-padding-y, 0.625rem) var(--code-padding-x, 0.75rem)",
207
- background: "transparent",
208
- color: SYNTAX.foreground,
209
- fontSize: "var(--code-font-size, 0.8125rem)",
210
- lineHeight: "var(--code-line-height, 1.5)",
211
- overflowX: "auto"
212
- };
213
- const codeStyle = {
214
- fontFamily: "var(--font-mono, 'JetBrains Mono', ui-monospace, monospace)",
215
- whiteSpace: "pre"
216
- };
217
41
  const isHighlighted = Boolean(highlighted && highlighted.code === code && highlighted.grammar === grammar);
218
42
  const lines = isHighlighted ? highlighted.lines.map((line) => line.map((token) => ({
219
43
  content: token.content,
@@ -221,113 +45,141 @@ const CodeBlock = memo(({ code, language, label, filename, showLineNumbers = fal
221
45
  }))) : code.split("\n").map((line) => [{ content: line }]);
222
46
  const regionName = filename ?? headerLabel;
223
47
  return /* @__PURE__ */ jsxs("div", {
224
- className: cn("group relative overflow-hidden rounded-lg border font-mono", "bg-card border-border", className),
48
+ className: cn(codePresentation.root, className),
225
49
  "data-highlighted": isHighlighted ? "true" : void 0,
226
50
  ...props,
227
51
  ...mode,
228
52
  children: [
229
53
  hasHeader && /* @__PURE__ */ jsxs("div", {
230
- className: "flex items-center justify-between gap-3 border-b border-border px-3 py-1.5 bg-[var(--code-header-bg,hsl(var(--background)))]",
231
- children: [filename ? /* @__PURE__ */ jsxs("span", {
232
- className: "flex min-w-0 items-center gap-2 font-sans text-sm",
233
- children: [
234
- /* @__PURE__ */ jsx(FileCode2, {
235
- "aria-hidden": true,
236
- className: "size-4 shrink-0 text-muted-foreground"
237
- }),
238
- /* @__PURE__ */ jsx("span", {
239
- className: "min-w-0 truncate font-mono font-medium text-foreground",
240
- title: filename,
241
- children: filename
242
- }),
243
- languageName && /* @__PURE__ */ jsx("span", {
244
- className: "shrink-0 text-muted-foreground",
245
- children: languageName
246
- })
247
- ]
248
- }) : /* @__PURE__ */ jsx("span", {
249
- className: "font-sans text-sm font-medium text-muted-foreground",
250
- children: headerLabel
251
- }), children]
54
+ className: codePresentation.header,
55
+ children: [
56
+ metadata && /* @__PURE__ */ jsx("span", {
57
+ className: "tangle-api-meta",
58
+ children: apiMetadataText(metadata)
59
+ }),
60
+ filename ? /* @__PURE__ */ jsxs("span", {
61
+ className: "flex min-w-0 items-center gap-2 font-sans text-sm",
62
+ children: [
63
+ /* @__PURE__ */ jsx(FileCode2, {
64
+ "aria-hidden": true,
65
+ className: "size-4 shrink-0 text-muted-foreground"
66
+ }),
67
+ /* @__PURE__ */ jsx("span", {
68
+ className: "min-w-0 truncate font-mono font-medium text-foreground",
69
+ title: filename,
70
+ children: filename
71
+ }),
72
+ languageName && /* @__PURE__ */ jsx("span", {
73
+ className: "shrink-0 text-muted-foreground",
74
+ children: languageName
75
+ })
76
+ ]
77
+ }) : /* @__PURE__ */ jsx("span", {
78
+ className: "font-sans text-sm font-medium text-muted-foreground",
79
+ children: headerLabel
80
+ }),
81
+ children,
82
+ examples?.length ? /* @__PURE__ */ jsx(CopyButton, { text: code }) : null
83
+ ]
252
84
  }),
253
85
  !hasHeader && children && /* @__PURE__ */ jsx("div", {
254
86
  className: "absolute right-2 top-2 z-10 flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100",
255
87
  children
256
88
  }),
257
- /* @__PURE__ */ jsx("pre", {
258
- tabIndex: 0,
259
- role: "region",
260
- "aria-label": regionName ? `${regionName} code` : "Code",
261
- className: focusRingInset,
262
- style: preStyle,
263
- children: /* @__PURE__ */ jsx("code", {
264
- style: codeStyle,
265
- children: !isHighlighted && !showLineNumbers ? code : lines.map((line, index) => /* @__PURE__ */ jsxs("span", { children: [
266
- showLineNumbers && /* @__PURE__ */ jsx("span", {
267
- "aria-hidden": "true",
268
- "data-line-number": true,
269
- className: "inline-block select-none pr-4 text-right",
270
- style: {
271
- color: SYNTAX.comment,
272
- minWidth: "2.5em"
273
- },
274
- children: index + 1
275
- }),
276
- line.map((token, tokenIndex) => /* @__PURE__ */ jsx("span", {
277
- style: token.style,
278
- children: token.content
279
- }, tokenIndex)),
280
- index < lines.length - 1 ? "\n" : null
281
- ] }, index))
89
+ examples && examples.length > 1 && /* @__PURE__ */ jsx("div", {
90
+ role: "tablist",
91
+ "aria-label": "Code examples",
92
+ ref: tabList,
93
+ className: codePresentation.tabs,
94
+ children: examples.map((item, index) => /* @__PURE__ */ jsx("button", {
95
+ id: `${tabId}-tab-${index}`,
96
+ type: "button",
97
+ role: "tab",
98
+ className: codePresentation.tab,
99
+ "data-state": index === selectedIndex ? "active" : "inactive",
100
+ "aria-selected": index === selectedIndex,
101
+ tabIndex: index === selectedIndex ? 0 : -1,
102
+ "aria-controls": `${tabId}-panel`,
103
+ children: item.label
104
+ }, index))
105
+ }),
106
+ /* @__PURE__ */ jsx("div", {
107
+ id: `${tabId}-panel`,
108
+ role: examples && examples.length > 1 ? "tabpanel" : void 0,
109
+ "aria-labelledby": examples && examples.length > 1 ? `${tabId}-tab-${selectedIndex}` : void 0,
110
+ children: /* @__PURE__ */ jsx("pre", {
111
+ tabIndex: 0,
112
+ role: "region",
113
+ "aria-label": regionName ? `${regionName} code` : "Code",
114
+ className: focusRingInset,
115
+ style: codePreStyle,
116
+ children: /* @__PURE__ */ jsx("code", {
117
+ style: codeTextStyle,
118
+ children: !isHighlighted && !showLineNumbers ? code : lines.map((line, index) => /* @__PURE__ */ jsxs("span", { children: [
119
+ showLineNumbers && /* @__PURE__ */ jsx("span", {
120
+ "aria-hidden": "true",
121
+ "data-line-number": true,
122
+ className: "inline-block select-none pr-4 text-right",
123
+ style: {
124
+ color: SYNTAX.comment,
125
+ minWidth: "2.5em"
126
+ },
127
+ children: index + 1
128
+ }),
129
+ line.map((token, tokenIndex) => /* @__PURE__ */ jsx("span", {
130
+ style: token.style,
131
+ children: token.content
132
+ }, tokenIndex)),
133
+ index < lines.length - 1 ? "\n" : null
134
+ ] }, index))
135
+ })
282
136
  })
283
137
  })
284
138
  ]
285
139
  });
286
140
  });
287
141
  CodeBlock.displayName = "CodeBlock";
288
- /** Copy-to-clipboard button for use inside CodeBlock. */
289
- const CopyButton = memo(({ text }) => {
290
- const [copied, setCopied] = useState(false);
291
- const [status, setStatus] = useState("");
292
- const timerRef = useRef(null);
142
+ /** Copy-to-clipboard button for use inside CodeBlock or other exact-text actions. */
143
+ const CopyButton = memo(({ text, label: actionLabel, copiedLabel = "Copied", failedLabel = "Copy failed", className, onClick, ...props }) => {
144
+ const [{ copied, status, pending, loading, longRunning }, setFeedback] = useState({
145
+ copied: false,
146
+ status: "",
147
+ pending: false
148
+ });
149
+ const button = useRef(null);
150
+ const feedback = useRef(null);
293
151
  useEffect(() => {
294
- return () => {
295
- if (timerRef.current) clearTimeout(timerRef.current);
296
- };
152
+ const controller = createCopyFeedback(setFeedback, void 0, button.current ?? void 0);
153
+ feedback.current = controller;
154
+ return () => controller.dispose();
297
155
  }, []);
298
- const handleCopy = useCallback(async () => {
299
- setStatus("");
300
- if (timerRef.current) clearTimeout(timerRef.current);
301
- try {
302
- await navigator.clipboard.writeText(text);
303
- setCopied(true);
304
- setStatus("Copied to clipboard");
305
- } catch (err) {
306
- console.warn("Clipboard write failed:", err);
307
- setCopied(false);
308
- setStatus("Copy failed");
309
- }
310
- timerRef.current = setTimeout(() => {
311
- setCopied(false);
312
- setStatus("");
313
- }, 2e3);
314
- }, [text]);
315
- const label = copied ? "Copied" : "Copy to clipboard";
316
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
156
+ const handleCopy = (event) => {
157
+ if (pending || props["aria-disabled"] === true || props["aria-disabled"] === "true") return;
158
+ onClick?.(event);
159
+ if (!event.defaultPrevented) feedback.current?.copy(text);
160
+ };
161
+ const label = copied ? copiedLabel : actionLabel && status === "Copy failed" ? failedLabel : actionLabel ?? "Copy to clipboard";
162
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("button", {
163
+ ...props,
164
+ ref: button,
165
+ "data-copy-loading": !!loading,
317
166
  type: "button",
318
167
  onClick: handleCopy,
319
- className: cn("flex items-center justify-center w-6 h-6 rounded-md bg-muted border border-border hover:border-[var(--border-strong)] transition-colors", focusRing),
320
- "aria-label": label,
168
+ className: cn(codePresentation.copy, actionLabel && "gap-2 px-3", focusRing, className),
169
+ "aria-label": pending ? "Copying to clipboard" : label,
170
+ "aria-busy": pending || props["aria-busy"],
171
+ "aria-disabled": pending || props["aria-disabled"],
321
172
  title: label,
322
- children: copied ? /* @__PURE__ */ jsx(Check, {
173
+ children: [copied ? /* @__PURE__ */ jsx(Check, {
323
174
  "aria-hidden": "true",
324
175
  className: "w-3.5 h-3.5 text-[var(--surface-success-text)]"
325
176
  }) : /* @__PURE__ */ jsx(Copy, {
326
177
  "aria-hidden": "true",
327
178
  className: "w-3.5 h-3.5 text-muted-foreground"
328
- })
179
+ }), actionLabel && /* @__PURE__ */ jsx("span", { children: label })]
329
180
  }), /* @__PURE__ */ jsx("span", {
330
- className: "sr-only",
181
+ className: codePresentation.copyStatus,
182
+ "data-copy-long-work": !!longRunning,
331
183
  role: "status",
332
184
  "aria-live": "polite",
333
185
  children: status
@@ -1,5 +1,7 @@
1
1
  import { Markdown, MarkdownLinkChip, MarkdownLinkChipResolver, MarkdownProps, getSanitizedMarkdownHeadingIdFromRawFragment } from "./markdown/markdown.js";
2
- import { CodeBlock, CodeBlockProps, CopyButton, codeBlockGrammar } from "./markdown/code-block.js";
2
+ import { codeBlockGrammar } from "./lib/code-presentation.js";
3
+ import { CodeBlock, CodeBlockProps, CopyButton, CopyButtonProps } from "./markdown/code-block.js";
3
4
  import { fenceInfo } from "./markdown/code-meta.js";
4
5
  import { numericColumns, tableCsv } from "./markdown/markdown-table.js";
5
- export { CodeBlock, type CodeBlockProps, CopyButton, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, codeBlockGrammar, fenceInfo, getSanitizedMarkdownHeadingIdFromRawFragment, numericColumns, tableCsv };
6
+ import { ApiSchema, ApiSchemaProps } from "./markdown/api-schema.js";
7
+ export { ApiSchema, type ApiSchemaProps, CodeBlock, type CodeBlockProps, CopyButton, type CopyButtonProps, Markdown, type MarkdownLinkChip, type MarkdownLinkChipResolver, type MarkdownProps, codeBlockGrammar, fenceInfo, getSanitizedMarkdownHeadingIdFromRawFragment, numericColumns, tableCsv };
package/dist/markdown.js CHANGED
@@ -1,5 +1,7 @@
1
- import { CodeBlock, CopyButton, codeBlockGrammar } from "./markdown/code-block.js";
1
+ import { codeBlockGrammar } from "./lib/code-presentation.js";
2
+ import { CodeBlock, CopyButton } from "./markdown/code-block.js";
2
3
  import { fenceInfo } from "./markdown/code-meta.js";
3
4
  import { numericColumns, tableCsv } from "./markdown/markdown-table.js";
4
5
  import { Markdown, getSanitizedMarkdownHeadingIdFromRawFragment } from "./markdown/markdown.js";
5
- export { CodeBlock, CopyButton, Markdown, codeBlockGrammar, fenceInfo, getSanitizedMarkdownHeadingIdFromRawFragment, numericColumns, tableCsv };
6
+ import { ApiSchema } from "./markdown/api-schema.js";
7
+ export { ApiSchema, CodeBlock, CopyButton, Markdown, codeBlockGrammar, fenceInfo, getSanitizedMarkdownHeadingIdFromRawFragment, numericColumns, tableCsv };
@@ -0,0 +1,88 @@
1
+ import { controlMotion } from "./lib/motion.js";
2
+ import { controlHeight, controlRadius, controlSizes, controlText, fieldPresentation, fieldSizes, fieldTouchText } from "./lib/control-presentation.js";
3
+ import { ixControlSoft, ixSelected, ixTabUnderline } from "./lib/interaction.js";
4
+ //#region src/presentation.d.ts
5
+ export declare const buttonVariants: (props?: ({
6
+ variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "sandbox" | "secondary" | null | undefined;
7
+ size?: "compact" | "default" | "display" | "icon" | "icon-lg" | "icon-sm" | "lg" | "md" | "sm" | "touch" | "xl" | null | undefined;
8
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
9
+ export declare const inputVariants: (props?: ({
10
+ variant?: "default" | "error" | "sandbox" | null | undefined;
11
+ size?: "compact" | "default" | "lg" | "md" | "sm" | "touch" | null | undefined;
12
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
13
+ export declare const tablePresentation: {
14
+ readonly root: "w-full caption-bottom text-sm";
15
+ readonly wrapper: "relative w-full overflow-auto";
16
+ readonly head: "[&_tr]:border-b";
17
+ readonly body: "[&_tr:last-child]:border-0";
18
+ readonly footer: "border-t bg-card font-medium [&>tr]:last:border-b-0";
19
+ readonly row: "border-border border-b [transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none hover:bg-muted/50 data-[state=selected]:bg-muted/50";
20
+ readonly columnHeader: "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0";
21
+ readonly cell: "p-4 align-middle [&:has([role=checkbox])]:pr-0";
22
+ readonly caption: "mt-4 text-muted-foreground text-sm";
23
+ };
24
+ export declare const emptyStatePresentation: {
25
+ readonly root: "flex flex-col items-center justify-center px-4 py-16 text-center";
26
+ readonly icon: "mb-4 rounded-full bg-muted p-4 text-muted-foreground";
27
+ readonly title: "font-semibold text-lg";
28
+ readonly description: "mt-2 max-w-sm text-muted-foreground text-sm";
29
+ readonly action: "mt-6";
30
+ };
31
+ export declare const metricPresentation: {
32
+ readonly strip: "grid min-w-0 rounded-[var(--radius-lg)] border border-border bg-card shadow-[var(--shadow-card)]";
33
+ readonly item: "min-w-0 border-border p-4 sm:p-5";
34
+ readonly value: "mt-1 whitespace-normal [overflow-wrap:anywhere] font-semibold text-xl tabular-nums tracking-tight lg:text-2xl";
35
+ readonly label: "flex min-w-0 flex-wrap items-center gap-2 text-muted-foreground text-sm";
36
+ readonly labelText: "min-w-0 whitespace-normal [overflow-wrap:anywhere]";
37
+ readonly hint: "mt-0.5 whitespace-normal [overflow-wrap:anywhere] text-[var(--text-dim)] text-xs";
38
+ };
39
+ export declare const metricLayouts: {
40
+ readonly 3: "grid-cols-2 sm:grid-cols-3 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:[&>div:not(:nth-child(3n+1))]:border-l";
41
+ readonly 4: "grid-cols-2 sm:grid-cols-4 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:[&>div:not(:nth-child(4n+1))]:border-l";
42
+ readonly 5: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:max-lg:[&>div:not(:nth-child(3n+1))]:border-l lg:[&>div:not(:nth-child(5n+1))]:border-l";
43
+ readonly 6: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 max-sm:[&>div:not(:nth-child(2n+1))]:border-l sm:max-lg:[&>div:not(:nth-child(3n+1))]:border-l lg:[&>div:not(:nth-child(6n+1))]:border-l";
44
+ };
45
+ export declare const dialogPresentation: {
46
+ readonly overlay: "fixed inset-0 z-50 bg-black/40";
47
+ readonly transition: "data-[state=open]:[animation:tangle-surface-present_var(--motion-surface)_both] data-[state=delayed-open]:[animation:tangle-surface-present_var(--motion-surface)_both] data-[state=instant-open]:[animation:tangle-surface-present_var(--motion-surface)_both] data-[state=closed]:[animation:tangle-surface-absent_var(--motion-dismiss)_both] motion-reduce:animate-none!";
48
+ readonly position: "fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%]";
49
+ readonly surface: "gap-4 rounded-lg border bg-card p-6 shadow-lg";
50
+ readonly close: "absolute top-4 right-4 rounded-sm opacity-70 [transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none hover:opacity-100 focus-visible:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground";
51
+ readonly header: "flex flex-col space-y-1.5 text-center sm:text-left";
52
+ readonly footer: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2";
53
+ readonly title: "font-semibold text-lg leading-none tracking-tight";
54
+ readonly description: "text-muted-foreground text-sm";
55
+ };
56
+ /** Native-dialog adapter for Hono/static markup. Radix remains the React Dialog/Sheet owner. */
57
+ export declare function bindDialog(dialog: HTMLDialogElement, trigger: HTMLElement): () => void;
58
+ export declare const labelVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
59
+ export declare const helpPresentation: {
60
+ root: string;
61
+ error: string;
62
+ hint: string;
63
+ };
64
+ export declare const cardPresentation: {
65
+ readonly root: "rounded-[var(--radius-lg)] border text-card-foreground [transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none";
66
+ readonly padding: "p-4";
67
+ readonly variants: {
68
+ readonly default: "bg-card border-border shadow-[var(--edge-highlight),var(--shadow-card)]";
69
+ readonly elevated: "bg-muted/50 border-border shadow-[var(--shadow-card)]";
70
+ readonly glass: "bg-card/80 backdrop-blur-xl border-border shadow-[var(--shadow-card)]";
71
+ readonly sandbox: "bg-muted/50 border-primary/20 shadow-[var(--shadow-accent)]";
72
+ };
73
+ };
74
+ /** Canonical segmented, pill and underline navigation presentation. */
75
+ export declare const tabsPresentation: {
76
+ readonly list: {
77
+ readonly default: "inline-flex h-10 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground";
78
+ readonly pills: "inline-flex items-center gap-2";
79
+ readonly underline: "inline-flex items-center gap-4 border-b border-border";
80
+ };
81
+ readonly trigger: {
82
+ readonly default: "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium [transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none focus-visible:outline-1 focus-visible:outline-[var(--focus-border)] focus-visible:ring-4 focus-visible:ring-[var(--focus-halo)] focus-visible:outline-offset-0 disabled:pointer-events-none disabled:opacity-50 hover:bg-[var(--ix-hover-bg)] active:bg-[var(--ix-sel-bg)] aria-pressed:bg-[var(--ix-sel-bg)] aria-pressed:text-[var(--accent-text)] aria-pressed:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] aria-checked:bg-[var(--ix-sel-bg)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] aria-[current=page]:bg-[var(--ix-sel-bg)] aria-[current=page]:text-[var(--accent-text)] aria-[current=page]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] data-[state=on]:bg-[var(--ix-sel-bg)] data-[state=on]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] data-[state=active]:bg-[var(--ix-sel-bg)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)]";
83
+ readonly pills: "inline-flex items-center justify-center whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium [transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none focus-visible:outline-1 focus-visible:outline-[var(--focus-border)] focus-visible:ring-4 focus-visible:ring-[var(--focus-halo)] focus-visible:outline-offset-0 disabled:pointer-events-none disabled:opacity-50 data-[state=inactive]:text-muted-foreground hover:bg-[var(--ix-hover-bg)] active:bg-[var(--ix-sel-bg)] aria-pressed:bg-[var(--ix-sel-bg)] aria-pressed:text-[var(--accent-text)] aria-pressed:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] aria-checked:bg-[var(--ix-sel-bg)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] aria-[current=page]:bg-[var(--ix-sel-bg)] aria-[current=page]:text-[var(--accent-text)] aria-[current=page]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] data-[state=on]:bg-[var(--ix-sel-bg)] data-[state=on]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)] data-[state=active]:bg-[var(--ix-sel-bg)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_0_0_1px_var(--ix-sel-ring)]";
84
+ readonly underline: "inline-flex items-center justify-center whitespace-nowrap rounded-t-md pb-3 text-sm font-medium [transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none focus-visible:outline-1 focus-visible:outline-[var(--focus-border)] focus-visible:ring-4 focus-visible:ring-[var(--focus-halo)] focus-visible:outline-offset-0 disabled:pointer-events-none disabled:opacity-50 border-b-2 border-transparent data-[state=inactive]:text-muted-foreground hover:shadow-[inset_0_-2px_0_hsl(var(--hsl-ring)/0.4)] data-[state=active]:bg-[hsl(var(--hsl-ring)/0.07)] data-[state=active]:text-[var(--accent-text)] data-[state=active]:shadow-[inset_0_-2px_0_var(--ix-sel-ring)] aria-checked:bg-[hsl(var(--hsl-ring)/0.07)] aria-checked:text-[var(--accent-text)] aria-checked:shadow-[inset_0_-2px_0_var(--ix-sel-ring)]";
85
+ };
86
+ };
87
+ //#endregion
88
+ export { controlHeight, controlMotion, controlRadius, controlSizes, controlText, fieldPresentation, fieldSizes, fieldTouchText, ixControlSoft, ixSelected, ixTabUnderline };