@keboola/design 11.6.1 → 11.9.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 (153) hide show
  1. package/AGENTS.md +16 -1
  2. package/CHANGELOG.md +86 -0
  3. package/COMPONENT-API.md +47 -46
  4. package/README.md +1 -1
  5. package/dist/{AIKit-CQxVwyAk.js → AIKit-ELIsegJz.js} +550 -103
  6. package/dist/AIKit-ELIsegJz.js.map +1 -0
  7. package/dist/{Alert-C4Y9IwMI.js → Alert-BsIM_oFK.js} +2 -2
  8. package/dist/{Alert-C4Y9IwMI.js.map → Alert-BsIM_oFK.js.map} +1 -1
  9. package/dist/{BrandEditor-CS9P4r2p.js → BrandEditor-BsTBM3s6.js} +4 -4
  10. package/dist/{BrandEditor-CS9P4r2p.js.map → BrandEditor-BsTBM3s6.js.map} +1 -1
  11. package/dist/{Breadcrumb-D1id7YXd.js → Breadcrumb-xqEUxfwn.js} +2 -2
  12. package/dist/{Breadcrumb-D1id7YXd.js.map → Breadcrumb-xqEUxfwn.js.map} +1 -1
  13. package/dist/{Checkbox-Bqr6zXcW.js → Checkbox-Dq5fPNrO.js} +2 -2
  14. package/dist/{Checkbox-Bqr6zXcW.js.map → Checkbox-Dq5fPNrO.js.map} +1 -1
  15. package/dist/{Clipboard-C2YBRfD8.js → Clipboard-BZitKiSA.js} +2 -2
  16. package/dist/{Clipboard-C2YBRfD8.js.map → Clipboard-BZitKiSA.js.map} +1 -1
  17. package/dist/{ClipboardButton-CrljClL1.js → ClipboardButton-KHQnPEh_.js} +2 -2
  18. package/dist/{ClipboardButton-CrljClL1.js.map → ClipboardButton-KHQnPEh_.js.map} +1 -1
  19. package/dist/{CodeEditor-C9AS8Ch0.js → CodeEditor-BJKJXA0c.js} +4 -4
  20. package/dist/{CodeEditor-C9AS8Ch0.js.map → CodeEditor-BJKJXA0c.js.map} +1 -1
  21. package/dist/{ColorInput-Dg87lKcR.js → ColorInput-Bj3dfPFA.js} +3 -3
  22. package/dist/{ColorInput-Dg87lKcR.js.map → ColorInput-Bj3dfPFA.js.map} +1 -1
  23. package/dist/{ColorModeSwitch-CsWLiTBX.js → ColorModeSwitch-CWt-DZGC.js} +2 -2
  24. package/dist/{ColorModeSwitch-CsWLiTBX.js.map → ColorModeSwitch-CWt-DZGC.js.map} +1 -1
  25. package/dist/{Combobox-BfanUAqY.js → Combobox-D8PrEqTG.js} +3 -3
  26. package/dist/{Combobox-BfanUAqY.js.map → Combobox-D8PrEqTG.js.map} +1 -1
  27. package/dist/{Command-CqxoI_De.js → Command-BDU3C1gg.js} +2 -2
  28. package/dist/{Command-CqxoI_De.js.map → Command-BDU3C1gg.js.map} +1 -1
  29. package/dist/{CopyField-Rx9HPTbJ.js → CopyField-BHRQUyuF.js} +2 -2
  30. package/dist/{CopyField-Rx9HPTbJ.js.map → CopyField-BHRQUyuF.js.map} +1 -1
  31. package/dist/{DateInput-DpifECUQ.js → DateInput-B6N8g0ra.js} +3 -3
  32. package/dist/{DateInput-DpifECUQ.js.map → DateInput-B6N8g0ra.js.map} +1 -1
  33. package/dist/{DateRangeInput-9MIw7P6L.js → DateRangeInput-7M9Z4cVO.js} +3 -3
  34. package/dist/{DateRangeInput-9MIw7P6L.js.map → DateRangeInput-7M9Z4cVO.js.map} +1 -1
  35. package/dist/{DropdownMenu-CASr-jgl.js → DropdownMenu-CgVwhDvl.js} +9 -4
  36. package/dist/DropdownMenu-CgVwhDvl.js.map +1 -0
  37. package/dist/{FieldItem-DtPTzNef.js → FieldItem-DZGIdiXE.js} +3 -3
  38. package/dist/{FieldItem-DtPTzNef.js.map → FieldItem-DZGIdiXE.js.map} +1 -1
  39. package/dist/{Form-DAaatlBk.js → Form-CcK7dRrC.js} +5 -5
  40. package/dist/{Form-DAaatlBk.js.map → Form-CcK7dRrC.js.map} +1 -1
  41. package/dist/{FormGroup-Bl8JmnzJ.js → FormGroup-DR_gZ25-.js} +3 -3
  42. package/dist/{FormGroup-Bl8JmnzJ.js.map → FormGroup-DR_gZ25-.js.map} +1 -1
  43. package/dist/{InputGroup-DcZRlVnN.js → InputGroup-Do8K2hvD.js} +2 -1
  44. package/dist/InputGroup-Do8K2hvD.js.map +1 -0
  45. package/dist/{Kbd-D1Azcc4g.js → Kbd-LCFMumoq.js} +2 -2
  46. package/dist/{Kbd-D1Azcc4g.js.map → Kbd-LCFMumoq.js.map} +1 -1
  47. package/dist/{MermaidDiagram-CujFxVzk.js → MermaidDiagram-ZG0iiDV_.js} +2 -2
  48. package/dist/{MermaidDiagram-CujFxVzk.js.map → MermaidDiagram-ZG0iiDV_.js.map} +1 -1
  49. package/dist/{Modal-Coj-H02o.js → Modal-I3VWk2oo.js} +2 -2
  50. package/dist/{Modal-Coj-H02o.js.map → Modal-I3VWk2oo.js.map} +1 -1
  51. package/dist/{ModalTitle-C76ke2eo.js → ModalTitle-CeLM_mX9.js} +2 -2
  52. package/dist/ModalTitle-CeLM_mX9.js.map +1 -0
  53. package/dist/{MultiActionButtonGroup-CNNoBQbv.js → MultiActionButtonGroup-CYZ8IgSf.js} +2 -2
  54. package/dist/{MultiActionButtonGroup-CNNoBQbv.js.map → MultiActionButtonGroup-CYZ8IgSf.js.map} +1 -1
  55. package/dist/{RadioGroup-CUBsuP7r.js → RadioGroup-Byc2mym4.js} +2 -2
  56. package/dist/{RadioGroup-CUBsuP7r.js.map → RadioGroup-Byc2mym4.js.map} +1 -1
  57. package/dist/{Search-DVpJuzr9.js → Search-Dhig_hPD.js} +2 -2
  58. package/dist/{Search-DVpJuzr9.js.map → Search-Dhig_hPD.js.map} +1 -1
  59. package/dist/{SearchInput-Q85p8Ews.js → SearchInput-92r2bkA7.js} +3 -3
  60. package/dist/{SearchInput-Q85p8Ews.js.map → SearchInput-92r2bkA7.js.map} +1 -1
  61. package/dist/{SectionNav-mpmSM3OC.js → SectionNav-CNLJsiVG.js} +3 -3
  62. package/dist/{SectionNav-mpmSM3OC.js.map → SectionNav-CNLJsiVG.js.map} +1 -1
  63. package/dist/{Sheet-Chf0kX1E.js → Sheet-OparjIi-.js} +2 -2
  64. package/dist/{Sheet-Chf0kX1E.js.map → Sheet-OparjIi-.js.map} +1 -1
  65. package/dist/{Sidebar-CTWsHZhF.js → Sidebar-CLEbBtNY.js} +190 -25
  66. package/dist/Sidebar-CLEbBtNY.js.map +1 -0
  67. package/dist/{Slider-Bv0B6lI0.js → Slider-DLclvRws.js} +2 -2
  68. package/dist/{Slider-Bv0B6lI0.js.map → Slider-DLclvRws.js.map} +1 -1
  69. package/dist/{TextInput-DiEMxVF6.js → TextInput-CFTf4Cz1.js} +5 -4
  70. package/dist/TextInput-CFTf4Cz1.js.map +1 -0
  71. package/dist/{ThemeProvider-BI6JWBdJ.js → ThemeProvider-DAmNn9Sd.js} +2 -2
  72. package/dist/{ThemeProvider-BI6JWBdJ.js.map → ThemeProvider-DAmNn9Sd.js.map} +1 -1
  73. package/dist/{Timeline-BG5Xogxh.js → Timeline-C8-wnRRr.js} +2 -2
  74. package/dist/{Timeline-BG5Xogxh.js.map → Timeline-C8-wnRRr.js.map} +1 -1
  75. package/dist/{ZoomableSvg-U_VH3QRO.js → ZoomableSvg-BZlk3PFJ.js} +3 -3
  76. package/dist/{ZoomableSvg-U_VH3QRO.js.map → ZoomableSvg-BZlk3PFJ.js.map} +1 -1
  77. package/dist/editors.js +2 -2
  78. package/dist/flint.js +1 -1
  79. package/dist/index.d.ts +900 -561
  80. package/dist/index.js +36 -34
  81. package/dist/internal/blocks/AIKit.js +2 -2
  82. package/dist/internal/blocks/BrandEditor.js +1 -1
  83. package/dist/internal/blocks/ChipEditor.js +137 -0
  84. package/dist/internal/blocks/ChipEditor.js.map +1 -0
  85. package/dist/internal/blocks/ClipboardButton.js +1 -1
  86. package/dist/internal/blocks/ColorInput.js +1 -1
  87. package/dist/internal/blocks/ColorModeSwitch.js +1 -1
  88. package/dist/internal/blocks/Command.js +1 -1
  89. package/dist/internal/blocks/CopyField.js +1 -1
  90. package/dist/internal/blocks/DateInput.js +1 -1
  91. package/dist/internal/blocks/DateRangeInput.js +1 -1
  92. package/dist/internal/blocks/ExportMenu.js +2 -2
  93. package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
  94. package/dist/internal/blocks/Form.js +1 -1
  95. package/dist/internal/blocks/FormGroup.js +1 -1
  96. package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
  97. package/dist/internal/blocks/MultiCombobox.js +18 -28
  98. package/dist/internal/blocks/MultiCombobox.js.map +1 -1
  99. package/dist/internal/blocks/NumberInput.js +1 -1
  100. package/dist/internal/blocks/PasswordInput.js +2 -2
  101. package/dist/internal/blocks/SearchInput.js +1 -1
  102. package/dist/internal/blocks/SearchInputWithNavigation.js +2 -2
  103. package/dist/internal/blocks/SectionNav.js +1 -1
  104. package/dist/internal/blocks/SplitButton.js +1 -1
  105. package/dist/internal/blocks/TextInput.js +1 -1
  106. package/dist/internal/blocks/Textarea.js +1 -1
  107. package/dist/internal/blocks/TimeInput.js +2 -2
  108. package/dist/internal/components/Alert.js +1 -1
  109. package/dist/internal/components/Avatar.js +1 -1
  110. package/dist/internal/components/Breadcrumb.js +1 -1
  111. package/dist/internal/components/Checkbox.js +1 -1
  112. package/dist/internal/components/Clipboard.js +1 -1
  113. package/dist/internal/components/CollapsiblePanel.js +1 -1
  114. package/dist/internal/components/Combobox.js +1 -1
  115. package/dist/internal/components/DropdownMenu.js +1 -1
  116. package/dist/internal/components/InputGroup.js +1 -1
  117. package/dist/internal/components/Kbd.js +1 -1
  118. package/dist/internal/components/Modal.js +1 -1
  119. package/dist/internal/components/RadioGroup.js +1 -1
  120. package/dist/internal/components/Search.js +1 -1
  121. package/dist/internal/components/Sheet.js +1 -1
  122. package/dist/internal/components/Sidebar.js +1 -1
  123. package/dist/internal/components/Slider.js +1 -1
  124. package/dist/internal/components/StatusDot.js +67 -0
  125. package/dist/internal/components/StatusDot.js.map +1 -0
  126. package/dist/internal/components/ThemeProvider.js +1 -1
  127. package/dist/internal/components/Timeline.js +1 -1
  128. package/dist/internal/components/Tooltip.js +2 -2
  129. package/dist/internal/components/Truncated.js +1 -1
  130. package/dist/internal/components/ZoomableSvg.js +1 -1
  131. package/dist/internal/hooks/useScrollbarPosition.js +1 -1
  132. package/dist/internal/utils/mergeRefs.js +27 -9
  133. package/dist/internal/utils/mergeRefs.js.map +1 -1
  134. package/dist/internal/utils.js +2 -2
  135. package/dist/markdown-editor.js +5 -5
  136. package/dist/mermaid.js +1 -1
  137. package/dist/rjsf.js +9 -9
  138. package/dist/styles.css +1 -1
  139. package/dist/testing.js +1 -1
  140. package/dist/useElement-BK3AAiIf.js +24 -0
  141. package/dist/useElement-BK3AAiIf.js.map +1 -0
  142. package/dist/{utils-DnkrFYqY.js → utils-BW4AE8VP.js} +31 -3
  143. package/dist/utils-BW4AE8VP.js.map +1 -0
  144. package/package.json +2 -1
  145. package/dist/AIKit-CQxVwyAk.js.map +0 -1
  146. package/dist/DropdownMenu-CASr-jgl.js.map +0 -1
  147. package/dist/InputGroup-DcZRlVnN.js.map +0 -1
  148. package/dist/ModalTitle-C76ke2eo.js.map +0 -1
  149. package/dist/Sidebar-CTWsHZhF.js.map +0 -1
  150. package/dist/TextInput-DiEMxVF6.js.map +0 -1
  151. package/dist/useElement-DkF6esWL.js +0 -15
  152. package/dist/useElement-DkF6esWL.js.map +0 -1
  153. package/dist/utils-DnkrFYqY.js.map +0 -1
@@ -1,23 +1,23 @@
1
1
  import { cn } from "./classNames-BzrkDnOD.js";
2
- import { Tooltip, TooltipProvider } from "./utils-DnkrFYqY.js";
3
- import { AlertCircle, ChevronDown, ChevronLeft, ChevronRight, CircleCheck, CirclePause, CircleX, Loader2, Maximize2, Minimize2, Play, Send, Sparkles, ThumbsDown, ThumbsUp, TriangleAlert, X, XCircle } from "./icons-Cek151jC.js";
2
+ import { Tooltip, TooltipProvider } from "./utils-BW4AE8VP.js";
3
+ import { AlertCircle, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Ban, BarChart3, Bell, Bug, Calendar, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CircleAlert, CircleCheck, CircleHelp, CirclePause, CirclePlay, CirclePlus, CircleStop, CircleX, Clock, Cog, Copy, Database, Download, Ellipsis, ExternalLink, Eye, File, FilePen, FileText, Flag, Folder, Gauge, GitBranch, GitMerge, GitPullRequest, Globe, Hand, History, Hourglass, Info, Key, Layers, LineChart, Link, List, ListChecks, Loader2, Lock, Mail, Maximize2, Minimize2, Minus, Pencil, Play, Plus, RefreshCw, Rocket, RotateCcw, RotateCw, Save, Search, Send, Settings, Sparkles, Star, Table, ThumbsDown, ThumbsUp, Trash2, TriangleAlert, Upload, User, Users, WandSparkles, Warehouse, Workflow, X, XCircle, Zap } from "./icons-Cek151jC.js";
4
4
  import { Button } from "./Button-DsRrU5F1.js";
5
5
  import { ButtonGroup } from "./internal/components/ButtonGroup.js";
6
- import { ClipboardButton } from "./ClipboardButton-CrljClL1.js";
6
+ import { ClipboardButton } from "./ClipboardButton-KHQnPEh_.js";
7
7
  import { FullScreenModal, FullScreenModalBody, FullScreenModalHeader } from "./FullScreenModal-CN9nAs72.js";
8
8
  import { Truncated } from "./internal/components/Truncated.js";
9
9
  import { HoverCard } from "./HoverCard-B9ZMJxjn.js";
10
- import { Command } from "./Command-CqxoI_De.js";
11
- import { TextInput } from "./TextInput-DiEMxVF6.js";
12
- import { Checkbox } from "./Checkbox-Bqr6zXcW.js";
10
+ import { Command } from "./Command-BDU3C1gg.js";
11
+ import { TextInput } from "./TextInput-CFTf4Cz1.js";
12
+ import { Checkbox } from "./Checkbox-Dq5fPNrO.js";
13
13
  import { Badge } from "./Badge-o5jURtpB.js";
14
14
  import { Textarea } from "./internal/blocks/Textarea.js";
15
- import { Table } from "./Table-CvYuTuNU.js";
15
+ import { Table as Table$1 } from "./Table-CvYuTuNU.js";
16
16
  import { DiagramSkeleton } from "./internal/blocks/DiagramShared/DiagramSkeleton.js";
17
17
  import { DiagramSource } from "./DiagramSource-C5_d8Gdj.js";
18
18
  import { Progress } from "./internal/components/Progress.js";
19
19
  import { Slot } from "@radix-ui/react-slot";
20
- import { isValidElement, useCallback, useEffect, useId, useRef, useState } from "react";
20
+ import { createContext, isValidElement, use, useCallback, useEffect, useId, useRef, useState } from "react";
21
21
  import { cva } from "class-variance-authority";
22
22
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
23
23
  import TextareaAutosize from "react-textarea-autosize";
@@ -26,13 +26,26 @@ import remarkGfm from "remark-gfm";
26
26
  //#region src/blocks/AIKit/AnswerActions.tsx
27
27
  const VOTE_BUTTONS = [{
28
28
  vote: "up",
29
- label: "Good answer",
30
29
  icon: /* @__PURE__ */ jsx(ThumbsUp, { className: "tw:size-4" })
31
30
  }, {
32
31
  vote: "down",
33
- label: "Bad answer",
34
32
  icon: /* @__PURE__ */ jsx(ThumbsDown, { className: "tw:size-4" })
35
33
  }];
34
+ const DEFAULT_LABELS = {
35
+ up: "Good answer",
36
+ down: "Bad answer"
37
+ };
38
+ /** Roomier for a panel a reader sits in; the compact one suits a row inside a dense transcript. */
39
+ const SIZES = {
40
+ small: {
41
+ button: "icon-xs",
42
+ gap: "extra-small"
43
+ },
44
+ medium: {
45
+ button: "icon-sm",
46
+ gap: "little-small"
47
+ }
48
+ };
36
49
  /**
37
50
  * What a reader can do with a finished answer: take it away, and say whether it was any good.
38
51
  *
@@ -45,31 +58,58 @@ const VOTE_BUTTONS = [{
45
58
  * - The recorded vote and the write are the caller's: the two chats vote against different
46
59
  * endpoints, and only the caller knows whether a turn is finished enough to rate.
47
60
  */
48
- const AnswerActions = ({ copyText, vote, onVote }) => {
61
+ const AnswerActions = ({ copyText, vote, onVote, onCopy, actions, labels = DEFAULT_LABELS, confirmation, size = "small", layout = "inline" }) => {
62
+ const { button, gap } = SIZES[size];
63
+ const votes = VOTE_BUTTONS.map(({ vote: candidate, icon }) => {
64
+ const isRecorded = vote === candidate;
65
+ const label = labels[candidate];
66
+ return /* @__PURE__ */ jsx(Tooltip, {
67
+ tooltip: label,
68
+ placement: "top",
69
+ triggerAsChild: true,
70
+ children: /* @__PURE__ */ jsx(Button, {
71
+ size: button,
72
+ variant: "invisible",
73
+ className: cn(isRecorded && candidate === "up" && "tw:bg-success/10 tw:text-success tw:hover:not-disabled:bg-success/20 tw:hover:not-disabled:text-success", isRecorded && candidate === "down" && "tw:bg-destructive/10 tw:text-destructive tw:hover:not-disabled:bg-destructive/20 tw:hover:not-disabled:text-destructive"),
74
+ "aria-label": label,
75
+ "aria-pressed": isRecorded,
76
+ onClick: () => onVote(candidate),
77
+ children: icon
78
+ })
79
+ }, candidate);
80
+ });
81
+ const copy = copyText ? /* @__PURE__ */ jsx(ClipboardButton, {
82
+ text: copyText,
83
+ tooltip: "Copy answer",
84
+ size: button,
85
+ variant: "invisible",
86
+ onClick: onCopy
87
+ }) : null;
88
+ const said = confirmation ? /* @__PURE__ */ jsx("span", {
89
+ "aria-live": "polite",
90
+ className: "tw:ml-1 tw:text-xs tw:font-medium tw:text-foreground tw:empty:hidden tw:animate-in tw:fade-in tw:slide-in-from-left-1 tw:duration-300",
91
+ children: vote ? confirmation[vote] : ""
92
+ }) : null;
93
+ if (layout === "split") return /* @__PURE__ */ jsxs("div", {
94
+ className: "tw:flex tw:w-full tw:items-center tw:justify-between tw:gap-2",
95
+ children: [/* @__PURE__ */ jsxs(ButtonGroup, {
96
+ space: gap,
97
+ "aria-label": "Answer actions",
98
+ children: [votes, said]
99
+ }), /* @__PURE__ */ jsxs(ButtonGroup, {
100
+ space: gap,
101
+ children: [actions, copy]
102
+ })]
103
+ });
49
104
  return /* @__PURE__ */ jsxs(ButtonGroup, {
50
- space: "extra-small",
105
+ space: gap,
51
106
  "aria-label": "Answer actions",
52
- children: [copyText ? /* @__PURE__ */ jsx(ClipboardButton, {
53
- text: copyText,
54
- tooltip: "Copy answer",
55
- size: "icon-xs",
56
- variant: "invisible"
57
- }) : null, VOTE_BUTTONS.map(({ vote: candidate, label, icon }) => {
58
- const isRecorded = vote === candidate;
59
- return /* @__PURE__ */ jsx(Tooltip, {
60
- tooltip: label,
61
- placement: "top",
62
- triggerAsChild: true,
63
- children: /* @__PURE__ */ jsx(Button, {
64
- size: "icon-xs",
65
- variant: isRecorded ? "secondary" : "invisible",
66
- "aria-label": label,
67
- "aria-pressed": isRecorded,
68
- onClick: () => onVote(candidate),
69
- children: icon
70
- })
71
- }, candidate);
72
- })]
107
+ children: [
108
+ copy,
109
+ votes,
110
+ actions,
111
+ said
112
+ ]
73
113
  });
74
114
  };
75
115
  //#endregion
@@ -98,6 +138,12 @@ const variantClasses = {
98
138
  dismissed: "tw:text-muted-foreground tw:focus-visible:outline-ring tw:not-disabled:hover:text-foreground"
99
139
  };
100
140
  const iconClasses = "tw:size-3.5 tw:shrink-0 tw:basis-3.5 tw:opacity-70";
141
+ const variantStatus = {
142
+ success: "finished",
143
+ error: "failed",
144
+ processing: "running",
145
+ dismissed: "not run"
146
+ };
101
147
  const renderVariantIcon$1 = (variant) => {
102
148
  if (variant === "processing") return /* @__PURE__ */ jsx(Loader2, { className: cn(iconClasses, "tw:animate-spin") });
103
149
  if (variant === "error" || variant === "dismissed") return /* @__PURE__ */ jsx(XCircle, { className: iconClasses });
@@ -108,13 +154,21 @@ const CollapsibleStepHeader = ({ isExpanded, onToggle, label, variant = "success
108
154
  type: "button",
109
155
  "aria-expanded": isExpanded,
110
156
  onClick: onToggle,
157
+ "data-variant": variant,
111
158
  className: cn("tw:flex tw:w-fit tw:items-center tw:gap-1.5 tw:text-left tw:text-xs tw:font-medium tw:transition-colors tw:focus-visible:outline tw:focus-visible:outline-2 tw:focus-visible:outline-offset-2 tw:disabled:opacity-40", variantClasses[variant]),
112
159
  children: [renderVariantIcon$1(variant), /* @__PURE__ */ jsxs("span", {
113
160
  className: "tw:min-w-0",
114
- children: [label, /* @__PURE__ */ jsxs("span", {
115
- className: "tw:whitespace-nowrap",
116
- children: ["⁠", /* @__PURE__ */ jsx(ChevronDown, { className: cn("tw:ml-1.5 tw:inline-block tw:size-3.5 tw:align-middle tw:transition-transform", isExpanded && "tw:rotate-180") })]
117
- })]
161
+ children: [
162
+ label,
163
+ /* @__PURE__ */ jsx("span", {
164
+ className: "tw:sr-only",
165
+ children: ` (${variantStatus[variant]})`
166
+ }),
167
+ /* @__PURE__ */ jsxs("span", {
168
+ className: "tw:whitespace-nowrap",
169
+ children: ["⁠", /* @__PURE__ */ jsx(ChevronDown, { className: cn("tw:ml-1.5 tw:inline-block tw:size-3.5 tw:align-middle tw:transition-transform", isExpanded && "tw:rotate-180") })]
170
+ })
171
+ ]
118
172
  })]
119
173
  });
120
174
  };
@@ -189,6 +243,250 @@ const AnswerSummary = ({ items, variant = "success", noAnswerLabel = "No answer"
189
243
  });
190
244
  };
191
245
  //#endregion
246
+ //#region src/blocks/AIKit/ChatInlineIcon.tsx
247
+ /**
248
+ * The Lucide component each `icon:<name>` token draws.
249
+ *
250
+ * - The names are FontAwesome-solid ones: kai-agent's prompt teaches that vocabulary, and
251
+ * `@keboola/agent-vocabulary` publishes it as `CHAT_INLINE_ICON_NAMES`.
252
+ * - `satisfies Record<ChatInlineIconName, LucideIcon>` is the drift guard — a name the prompt
253
+ * teaches and this map lacks is a compile error, not a token that silently disappears from an
254
+ * answer.
255
+ * - Several names map to one component on purpose: FA distinguishes `gear` from `cog` and
256
+ * `warning` from `triangle-exclamation`; Lucide draws each pair with one icon.
257
+ * - Read through `Object.hasOwn`, never by plain index: a model-emitted `icon:constructor`
258
+ * otherwise answers with an `Object.prototype` member and React tries to render it.
259
+ */
260
+ const ICON_REGISTRY = {
261
+ "alert-circle": AlertCircle,
262
+ "arrow-down": ArrowDown,
263
+ "arrow-left": ArrowLeft,
264
+ "arrow-right": ArrowRight,
265
+ "arrow-rotate-back": RotateCcw,
266
+ "arrow-rotate-left": RotateCcw,
267
+ "arrow-rotate-right": RotateCw,
268
+ "arrow-up": ArrowUp,
269
+ "arrow-up-right-from-square": ExternalLink,
270
+ "arrows-rotate": RefreshCw,
271
+ analytics: BarChart3,
272
+ ban: Ban,
273
+ bell: Bell,
274
+ bug: Bug,
275
+ calendar: Calendar,
276
+ changes: FilePen,
277
+ "chart-line": LineChart,
278
+ check: Check,
279
+ "check-circle": CircleCheck,
280
+ "chevron-down": ChevronDown,
281
+ "chevron-left": ChevronLeft,
282
+ "chevron-right": ChevronRight,
283
+ "chevron-up": ChevronUp,
284
+ circle: Circle,
285
+ "circle-check": CircleCheck,
286
+ "circle-exclamation": CircleAlert,
287
+ "circle-info": Info,
288
+ "circle-pause": CirclePause,
289
+ "circle-play": CirclePlay,
290
+ "circle-plus": CirclePlus,
291
+ "circle-question": CircleHelp,
292
+ "circle-stop": CircleStop,
293
+ "circle-xmark": CircleX,
294
+ clock: Clock,
295
+ "clock-rotate-left": History,
296
+ "code-branch": GitBranch,
297
+ "code-merge": GitMerge,
298
+ "code-pull-request": GitPullRequest,
299
+ cog: Cog,
300
+ configuration: Settings,
301
+ copy: Copy,
302
+ database: Database,
303
+ "diagram-project": Workflow,
304
+ download: Download,
305
+ ellipsis: Ellipsis,
306
+ envelope: Mail,
307
+ "exclamation-circle": CircleAlert,
308
+ "exclamation-triangle": TriangleAlert,
309
+ eye: Eye,
310
+ file: File,
311
+ "file-lines": FileText,
312
+ flag: Flag,
313
+ folder: Folder,
314
+ gauge: Gauge,
315
+ gear: Settings,
316
+ globe: Globe,
317
+ hand: Hand,
318
+ "hourglass-half": Hourglass,
319
+ "info-circle": Info,
320
+ key: Key,
321
+ "layer-group": Layers,
322
+ link: Link,
323
+ "list-check": ListChecks,
324
+ "list-ul": List,
325
+ lock: Lock,
326
+ "magnifying-glass": Search,
327
+ minus: Minus,
328
+ "paper-plane-top": Send,
329
+ pen: Pencil,
330
+ play: Play,
331
+ plus: Plus,
332
+ refresh: RefreshCw,
333
+ rocket: Rocket,
334
+ save: Save,
335
+ "save-changes": Save,
336
+ search: Search,
337
+ spinner: Loader2,
338
+ sparkles: Sparkles,
339
+ star: Star,
340
+ stop: CircleStop,
341
+ table: Table,
342
+ "thumbs-down": ThumbsDown,
343
+ "thumbs-up": ThumbsUp,
344
+ transformation: WandSparkles,
345
+ trash: Trash2,
346
+ "triangle-exclamation": TriangleAlert,
347
+ upload: Upload,
348
+ user: User,
349
+ "user-group": Users,
350
+ warning: TriangleAlert,
351
+ warehouse: Warehouse,
352
+ xmark: X,
353
+ zap: Zap
354
+ };
355
+ /**
356
+ * Extra classes a single name needs on top of the shared list.
357
+ *
358
+ * - `spinner` is the only one: `MAINTAINING.md` maps it to `Loader2` *plus* `tw:animate-spin`,
359
+ * because Lucide draws the arc and leaves the motion to the caller. A still `Loader2` reads as
360
+ * a damaged ring, not as waiting.
361
+ * - Read through `Object.hasOwn`, for the same reason the registry is.
362
+ */
363
+ const ICON_CLASSES = { spinner: "tw:animate-spin" };
364
+ /**
365
+ * A whole `icon:<name>` token and nothing else.
366
+ *
367
+ * - Anchored at both ends, and over the same name shape `remarkIconSyntax` (in
368
+ * `@keboola/kai-chat`) matches when it turns bare text into an `inlineCode` node. The two
369
+ * have to agree: a looser reader here accepts inline code the plugin never produced.
370
+ * - That is what keeps ordinary inline code ordinary. A prefix test alone claims
371
+ * `` `icon: database` ``, `` `icon:` `` and a fence body starting `icon:` — text the plugin
372
+ * never turned into a token, and none of it is an icon the author asked for.
373
+ */
374
+ const TOKEN = /^icon:([a-z][a-z0-9-]*)$/;
375
+ /** Built once: an answer is re-parsed per streamed chunk, so `isArriving` runs per code node. */
376
+ const ICON_NAMES = Object.keys(ICON_REGISTRY);
377
+ /**
378
+ * Whether a name is only ever the start of one this registry draws.
379
+ *
380
+ * - Strictly shorter, so a whole name is never read as an unfinished one.
381
+ * - `tabl` → `true` (`table`); `unicorn` and `table-name` → `false`.
382
+ */
383
+ const isArriving = (name) => ICON_NAMES.some((key) => key.length > name.length && key.startsWith(name));
384
+ /**
385
+ * The icon name in a code node's text, if the text is a token — drawn, or still arriving.
386
+ *
387
+ * - `icon:table` → `table`; anything else, including `icon: table`, `icon:Table` and
388
+ * `icon:unicorn` → `undefined`.
389
+ * - A name the registry lacks answers `undefined` on purpose, so every call site's own `<code>`
390
+ * fall-through keeps the text its author typed. `icon:` is an ordinary way to name a property,
391
+ * and this renderer draws vendor descriptions and dashboard text as well as agent answers.
392
+ * - A name that only starts one it draws is a token still arriving, answered with so that
393
+ * `<ChatInlineIcon />` draws nothing: an answer streams `icon:t`, `icon:ta`, `icon:tab` before
394
+ * the name lands, and each of those as code is a chip that reflows the line for a frame.
395
+ * - A name that is both — `save`, the start of `save-changes` — is drawn now; waiting would leave
396
+ * a finished token blank for the rest of the answer.
397
+ * - The token reaches a renderer as inline code either because the model wrote it in backticks
398
+ * or because a remark plugin turned the bare text into an `inlineCode` node. Parsing the
399
+ * token is the vocabulary's; producing it is the app's, since the plugin is workspace-internal.
400
+ */
401
+ const getChatInlineIconName = (text) => {
402
+ const name = TOKEN.exec(text)?.[1];
403
+ if (name === void 0) return void 0;
404
+ return Object.hasOwn(ICON_REGISTRY, name) || isArriving(name) ? name : void 0;
405
+ };
406
+ /**
407
+ * One inline `icon:<name>` token, drawn at the size of the text it leads.
408
+ *
409
+ * - An unknown name renders nothing, which is how a token still arriving (`icon:tabl`) stays
410
+ * blank instead of flashing as a code chip. A name `getChatInlineIconName` withholds keeps its
411
+ * text at the call site's own `<code>` fall-through and never reaches here.
412
+ * - `size-[1em]` tracks the font size, so the icon matches a heading instead of sitting small
413
+ * beside it, and `align-[-0.125em]` on an inline-block optically centres it against the
414
+ * baseline.
415
+ * - `mr` tops up the single source space, which is a word gap and reads as cramped between a
416
+ * glyph and the phrase it labels.
417
+ * - A heavier stroke so an outline icon holds its own next to the bold weight a heading carries.
418
+ * - `spinner` also spins; the mapping owns that, not the caller. See `ICON_CLASSES`.
419
+ * - `aria-hidden`, because the glyph is decorative: the token's text is removed from the answer and
420
+ * nothing replaces it as a label, so a reader that announces unnamed inline SVGs would read an
421
+ * unlabelled graphic before every phrase the agent decorates.
422
+ */
423
+ const ChatInlineIcon = ({ name }) => {
424
+ const Icon = Object.hasOwn(ICON_REGISTRY, name) ? ICON_REGISTRY[name] : void 0;
425
+ if (Icon === void 0) return null;
426
+ return /* @__PURE__ */ jsx(Icon, {
427
+ "aria-hidden": true,
428
+ className: cn("tw:mr-[0.15em] tw:inline-block tw:size-[1em] tw:shrink-0 tw:align-[-0.125em] tw:text-muted-foreground", Object.hasOwn(ICON_CLASSES, name) ? ICON_CLASSES[name] : void 0),
429
+ strokeWidth: 2.25
430
+ });
431
+ };
432
+ //#endregion
433
+ //#region src/blocks/AIKit/InlineStepGroup.tsx
434
+ /**
435
+ * The surface colour, as the custom property the step icons mask against.
436
+ *
437
+ * - Annotated rather than asserted: `CSSProperties` admits no custom property of its own, and
438
+ * an intersection with the one this sets keeps the value type-checked at the assignment.
439
+ */
440
+ const surfaceStyle = (surface) => ({ "--inline-step-surface": surface });
441
+ /**
442
+ * A run of steps an agent took, behind one line that says how far along it is.
443
+ *
444
+ * - The frame only. What a step *says* is the host's — one product names buckets and tables,
445
+ * another must never put those words on screen — so the rows arrive as children and this
446
+ * supplies the header, the collapse, the connector and the rhythm they share.
447
+ * - One rule behind the whole run at the icon centre line: the list's `pl-5` plus half a
448
+ * `size-5` icon box. Drawn once rather than per row, so it does not vanish beside a row that
449
+ * happens to have no icon.
450
+ * - `grid-template-rows` rather than height, because the rows are text of unknown length and a
451
+ * transition needs a number at both ends. The children stay mounted while closed: a group the
452
+ * reader reopens must not re-run whatever mounting a row costs. A closed group is `inert` and
453
+ * `aria-hidden`, so staying mounted does not mean staying reachable.
454
+ * - Expansion is the caller's state. Whether a group opens itself while work is happening, and
455
+ * whether a reader's own toggle outranks that, is a transcript-level decision.
456
+ * - `role="group"` rather than the `<section>`'s own landmark semantics: a transcript draws one
457
+ * of these per run of steps, so a long conversation would hand a screen reader a landmark
458
+ * list of dozens of identically named `region`s and drown the page's real landmarks in it.
459
+ * The name still reads — a named `group` is announced by it — and it is the role the
460
+ * grounding panel already gives its repeated named answer sections.
461
+ */
462
+ const InlineStepGroup = ({ label, variant = "success", isExpanded, onToggle, surface = "rgb(var(--background))", "aria-label": ariaLabel, "data-testid": testId, className, children }) => /* @__PURE__ */ jsxs("section", {
463
+ role: "group",
464
+ className: cn("tw:flex tw:flex-col", className),
465
+ "aria-label": ariaLabel,
466
+ "data-testid": testId,
467
+ children: [/* @__PURE__ */ jsx(CollapsibleStepHeader, {
468
+ isExpanded,
469
+ onToggle,
470
+ label,
471
+ variant
472
+ }), /* @__PURE__ */ jsx("div", {
473
+ className: cn("tw:grid tw:transition-[grid-template-rows] tw:duration-200 tw:ease-out tw:motion-reduce:transition-none", isExpanded ? "tw:grid-rows-[1fr]" : "tw:grid-rows-[0fr]"),
474
+ children: /* @__PURE__ */ jsx("div", {
475
+ className: "tw:min-h-0 tw:overflow-hidden",
476
+ inert: !isExpanded,
477
+ "aria-hidden": isExpanded ? void 0 : true,
478
+ children: /* @__PURE__ */ jsxs("div", {
479
+ style: surfaceStyle(surface),
480
+ className: "tw:relative tw:flex tw:flex-col tw:gap-4 tw:pt-4 tw:pl-5",
481
+ children: [/* @__PURE__ */ jsx("span", {
482
+ "aria-hidden": "true",
483
+ className: "tw:pointer-events-none tw:absolute tw:top-4 tw:bottom-0 tw:left-[30px] tw:w-px tw:bg-border"
484
+ }), children]
485
+ })
486
+ })
487
+ })]
488
+ });
489
+ //#endregion
192
490
  //#region src/blocks/AIKit/DiagramBlock.tsx
193
491
  /**
194
492
  * One diagram block: a skeleton while its chunk loads, the fence source plus a retry if it fails.
@@ -318,6 +616,41 @@ const DIAGRAM_CHUNKS = {
318
616
  * every other app in the same bundle holds a fence back.
319
617
  */
320
618
  const DIAGRAM_LANGUAGES = Object.freeze(Object.keys(DIAGRAM_CHUNKS));
619
+ /**
620
+ * Whether the `code` being rendered sits inside a fenced block.
621
+ *
622
+ * - A fence with no info string reaches `code` with no `language-…` class, exactly like inline
623
+ * code, so the class cannot tell the two apart — `pre` is the only boundary that can.
624
+ * - Set by the `pre` renderer, read by `code`: React renders `pre` before its children, so the
625
+ * value is in place by the time the `code` inside it renders.
626
+ */
627
+ const CodeBlockContext = createContext(false);
628
+ /**
629
+ * The `code` renderer: an `icon:<name>` token becomes the icon it names, anything else stays code.
630
+ *
631
+ * - Only inline code is a candidate: a fenced block's body is code the reader asked to see, and
632
+ * swapping it for a glyph would drop it. `CodeBlockContext` is what separates the two — a fence
633
+ * with no info string carries no `language-…` class either, so the class cannot.
634
+ * - The token reaches here as inline code because `remarkIconSyntax` (in `@keboola/kai-chat`)
635
+ * turned the bare text into one, or because the model wrote the backticks itself. Producing the
636
+ * token is the app's, drawing it is this package's.
637
+ * - A name no icon answers to stays code: `getChatInlineIconName` answers only for names the
638
+ * registry can draw or is still receiving, and this renderer draws vendor descriptions and
639
+ * dashboard text too, where `icon:` is an ordinary way to name a property.
640
+ * - A name still arriving draws nothing, so a streamed `icon:tabl` is blank for a frame rather
641
+ * than a code chip that reflows the line.
642
+ * - A component rather than an inline renderer so it can read the context.
643
+ */
644
+ const MarkdownCode = ({ children, className, ...props }) => {
645
+ const { node: _node, ...rest } = props;
646
+ const name = use(CodeBlockContext) ? void 0 : getChatInlineIconName(getText(children));
647
+ if (name !== void 0) return /* @__PURE__ */ jsx(ChatInlineIcon, { name });
648
+ return /* @__PURE__ */ jsx("code", {
649
+ className,
650
+ ...rest,
651
+ children
652
+ });
653
+ };
321
654
  const baseComponents = {
322
655
  pre({ children, ...props }) {
323
656
  const child = Array.isArray(children) ? children[0] : children;
@@ -344,10 +677,14 @@ const baseComponents = {
344
677
  })
345
678
  }), /* @__PURE__ */ jsx("pre", {
346
679
  ...rest,
347
- children
680
+ children: /* @__PURE__ */ jsx(CodeBlockContext, {
681
+ value: true,
682
+ children
683
+ })
348
684
  })]
349
685
  });
350
686
  },
687
+ code: MarkdownCode,
351
688
  a({ children, href, ...props }) {
352
689
  const { node: _node, ...rest } = props;
353
690
  const isExternal = !!href && /^https?:\/\//.test(href);
@@ -365,7 +702,7 @@ const baseComponents = {
365
702
  const { node: _node, ...rest } = props;
366
703
  return /* @__PURE__ */ jsx("div", {
367
704
  className: "tw:not-prose tw:my-4",
368
- children: /* @__PURE__ */ jsx(Table, {
705
+ children: /* @__PURE__ */ jsx(Table$1, {
369
706
  bordered: true,
370
707
  className: cn("tw:!mb-0", className),
371
708
  ...rest
@@ -374,23 +711,23 @@ const baseComponents = {
374
711
  },
375
712
  thead(props) {
376
713
  const { node: _node, ...rest } = props;
377
- return /* @__PURE__ */ jsx(Table.Header, { ...rest });
714
+ return /* @__PURE__ */ jsx(Table$1.Header, { ...rest });
378
715
  },
379
716
  tbody(props) {
380
717
  const { node: _node, ...rest } = props;
381
- return /* @__PURE__ */ jsx(Table.Body, { ...rest });
718
+ return /* @__PURE__ */ jsx(Table$1.Body, { ...rest });
382
719
  },
383
720
  tr(props) {
384
721
  const { node: _node, ...rest } = props;
385
- return /* @__PURE__ */ jsx(Table.Row, { ...rest });
722
+ return /* @__PURE__ */ jsx(Table$1.Row, { ...rest });
386
723
  },
387
724
  th(props) {
388
725
  const { node: _node, ...rest } = props;
389
- return /* @__PURE__ */ jsx(Table.Head, { ...rest });
726
+ return /* @__PURE__ */ jsx(Table$1.Head, { ...rest });
390
727
  },
391
728
  td(props) {
392
729
  const { node: _node, ...rest } = props;
393
- return /* @__PURE__ */ jsx(Table.Cell, { ...rest });
730
+ return /* @__PURE__ */ jsx(Table$1.Cell, { ...rest });
394
731
  }
395
732
  };
396
733
  /**
@@ -910,8 +1247,45 @@ const PromptInputAction = ({ ref, onMouseDown, ...rest }) => {
910
1247
  };
911
1248
  //#endregion
912
1249
  //#region src/blocks/AIKit/PromptInputCommands.tsx
913
- const PromptInputCommands = ({ items, open, onSelect, filterValue, children }) => {
1250
+ /** What the menu needs above or below the composer before it will open on that side. */
1251
+ const MENU_HEIGHT = 440;
1252
+ const PromptInputCommands = ({ items, open, onSelect, filterValue, emptyText = "No commands found", onClose, children }) => {
914
1253
  const filtered = filterValue ? items.filter((item) => item.label.toLowerCase().includes(filterValue.toLowerCase()) || item.value.toLowerCase().includes(filterValue.toLowerCase())) : items;
1254
+ const [highlighted, setHighlighted] = useState();
1255
+ /**
1256
+ * Which side the menu opens on.
1257
+ *
1258
+ * - Above by default: a composer usually sits at the foot of its panel.
1259
+ * - Below when there is not room above and there is room below — the dashboard composer sits
1260
+ * near the top of the page, where opening upward clipped the list against the header.
1261
+ * - Re-measured while it is open: the composer grows as the message does, and the transcript
1262
+ * above it moves, so the side that fit on open may not fit a moment later.
1263
+ */
1264
+ const rootRef = useRef(null);
1265
+ const [below, setBelow] = useState(false);
1266
+ const measure = useCallback(() => {
1267
+ const box = rootRef.current?.getBoundingClientRect();
1268
+ if (!box) return;
1269
+ setBelow(box.top < MENU_HEIGHT && window.innerHeight - box.bottom > MENU_HEIGHT);
1270
+ }, []);
1271
+ const attachRoot = useCallback((node) => {
1272
+ rootRef.current = node;
1273
+ if (node) measure();
1274
+ }, [measure]);
1275
+ useEffect(() => {
1276
+ if (!open) return;
1277
+ const node = rootRef.current;
1278
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : null;
1279
+ if (node) observer?.observe(node);
1280
+ window.addEventListener("resize", measure);
1281
+ window.addEventListener("scroll", measure, true);
1282
+ return () => {
1283
+ observer?.disconnect();
1284
+ window.removeEventListener("resize", measure);
1285
+ window.removeEventListener("scroll", measure, true);
1286
+ };
1287
+ }, [open, measure]);
1288
+ const highlightedValue = filtered.some((item) => item.value === highlighted) ? highlighted : filtered[0]?.value;
915
1289
  const groups = filtered.reduce((acc, item) => {
916
1290
  const group = acc[item.group] ?? [];
917
1291
  group.push(item);
@@ -919,22 +1293,36 @@ const PromptInputCommands = ({ items, open, onSelect, filterValue, children }) =
919
1293
  return acc;
920
1294
  }, {});
921
1295
  return /* @__PURE__ */ jsxs(Command, {
1296
+ ref: attachRoot,
922
1297
  loop: true,
923
1298
  className: "tw:relative",
924
1299
  shouldFilter: false,
925
- children: [open && /* @__PURE__ */ jsx("div", {
926
- className: cn("tw:absolute tw:bottom-full tw:left-0 tw:right-0 tw:z-50 tw:mb-1", "tw:rounded-md tw:border tw:border-border tw:bg-popover tw:text-popover-foreground tw:shadow-lg"),
927
- children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", {
1300
+ value: highlightedValue ?? "",
1301
+ onValueChange: setHighlighted,
1302
+ children: [open && /* @__PURE__ */ jsxs("div", {
1303
+ className: cn("tw:absolute tw:left-0 tw:z-50 tw:w-full tw:max-w-lg", below ? "tw:top-full tw:mt-1" : "tw:bottom-full tw:mb-1", "tw:rounded-[14px] tw:border tw:border-border tw:bg-popover tw:text-popover-foreground tw:shadow-lg", "tw:p-1"),
1304
+ children: [onClose && /* @__PURE__ */ jsx(Button, {
1305
+ type: "button",
1306
+ size: "icon-xs",
1307
+ variant: "invisible",
1308
+ "aria-label": "Close",
1309
+ className: "tw:absolute tw:right-2 tw:top-2 tw:z-10",
1310
+ onMouseDown: (event) => event.preventDefault(),
1311
+ onClick: onClose,
1312
+ children: /* @__PURE__ */ jsx(X, {})
1313
+ }), filtered.length === 0 ? /* @__PURE__ */ jsx("div", {
928
1314
  className: "tw:px-4 tw:py-3 tw:text-center tw:text-sm tw:text-muted-foreground",
929
- children: "No commands found"
1315
+ children: emptyText
930
1316
  }) : /* @__PURE__ */ jsx(Command.List, {
931
- viewPortClassName: "tw:max-h-80",
932
- children: Object.entries(groups).map(([groupName, groupItems]) => /* @__PURE__ */ jsx(Command.Group, {
1317
+ className: "tw:group-data-[variant=argo]/command:px-1 tw:group-data-[variant=argo]/command:pb-1",
1318
+ viewPortClassName: "tw:max-h-[26rem]",
1319
+ children: Object.entries(groups).map(([groupName, groupItems], index) => /* @__PURE__ */ jsx(Command.Group, {
933
1320
  heading: groupName,
1321
+ className: cn("tw:[&_[cmdk-group-heading]]:normal-case", "tw:[&_[cmdk-group-heading]]:tracking-normal", "tw:[&_[cmdk-group-heading]]:border-b-0", "tw:[&_[cmdk-group-heading]]:!py-0", "tw:[&_[cmdk-group-heading]]:mt-2", "tw:[&_[cmdk-group-heading]]:mb-1", "tw:[&_[cmdk-group-heading]]:pl-2", "tw:[&_[cmdk-group-heading]]:pr-9", "tw:[&_[cmdk-group-heading]]:text-xs", index > 0 && "tw:mt-3"),
934
1322
  children: groupItems.map((item) => /* @__PURE__ */ jsx(Command.Item, {
935
1323
  value: item.value,
936
- icon: item.icon,
937
- onSelect: () => onSelect(item.value),
1324
+ className: "tw:rounded-md",
1325
+ onSelect: () => onSelect(item),
938
1326
  children: /* @__PURE__ */ jsxs("div", {
939
1327
  className: "tw:flex tw:min-w-0 tw:flex-1 tw:flex-col",
940
1328
  children: [/* @__PURE__ */ jsx("span", { children: item.label }), item.description && /* @__PURE__ */ jsx(Truncated, {
@@ -945,11 +1333,32 @@ const PromptInputCommands = ({ items, open, onSelect, filterValue, children }) =
945
1333
  })
946
1334
  }, item.value))
947
1335
  }, groupName))
948
- })
1336
+ })]
949
1337
  }), children]
950
1338
  });
951
1339
  };
952
1340
  //#endregion
1341
+ //#region src/blocks/AIKit/PromptInputMenuTrigger.tsx
1342
+ /**
1343
+ * The composer's "add to message" button.
1344
+ *
1345
+ * - One glyph for both states: a plus rotated 45° reads as a close cross, so nothing swaps.
1346
+ * - `aria-expanded` mirrors `open`; the accessible name changes with it.
1347
+ * - Wrapped in `PromptInputAction` so a click does not hand focus to the textarea.
1348
+ */
1349
+ const PromptInputMenuTrigger = ({ open = false, large = false, className, ref, ...props }) => /* @__PURE__ */ jsx(PromptInputAction, { children: /* @__PURE__ */ jsx(Button, {
1350
+ ref,
1351
+ type: "button",
1352
+ size: large ? "icon" : "icon-sm",
1353
+ variant: "invisible",
1354
+ "aria-expanded": open,
1355
+ "aria-label": open ? "Close menu" : "Add to message",
1356
+ "data-testid": "prompt-input-menu-trigger",
1357
+ className: cn("tw:rounded-full", "tw:hover:not-disabled:bg-muted tw:hover:not-disabled:text-foreground tw:hover:not-disabled:border-transparent", "tw:aria-expanded:bg-muted", className),
1358
+ ...props,
1359
+ children: /* @__PURE__ */ jsx(Plus, { className: cn(large ? "tw:size-5" : "tw:size-4", "tw:transition-transform tw:duration-300 tw:ease-out tw:motion-reduce:transition-none", open && "tw:rotate-135") })
1360
+ }) });
1361
+ //#endregion
953
1362
  //#region src/blocks/AIKit/PromptInputSubmit.tsx
954
1363
  const ARIA_LABEL_BY_STATUS = {
955
1364
  ready: "Send message",
@@ -1024,11 +1433,7 @@ const promptInputVariants = cva([
1024
1433
  "tw:pt-3",
1025
1434
  "tw:pb-2"
1026
1435
  ],
1027
- inline: [
1028
- "tw:flex-row",
1029
- "tw:py-3",
1030
- "tw:items-center"
1031
- ]
1436
+ inline: ["tw:flex-col", "tw:py-3"]
1032
1437
  } }
1033
1438
  });
1034
1439
  const wrapperVariants = cva(null, {
@@ -1038,7 +1443,7 @@ const wrapperVariants = cva(null, {
1038
1443
  inline: ""
1039
1444
  } }
1040
1445
  });
1041
- const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, disabled, disableSubmit, value = "", onValueChange, status = "ready", maxRows = 6, minRows, commands, autoFocus, textareaRef: publishedRef, ...textareaProps }) => {
1446
+ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, attachments, className, disabled, disableSubmit, value = "", onValueChange, status = "ready", maxRows = 6, minRows, commands, autoFocus, textareaRef: publishedRef, ...textareaProps }) => {
1042
1447
  const defaultMinRows = variant === "inline" ? 1 : 2;
1043
1448
  const isDisabled = (disabled || disableSubmit || !value.trim()) && status !== "streaming" && status !== "interrupted";
1044
1449
  const textareaId = useId();
@@ -1053,10 +1458,11 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, dis
1053
1458
  if (autoFocusRef.current) textareaRef.current?.focus({ preventScroll: true });
1054
1459
  }, []);
1055
1460
  if (dismissedValue !== null && value !== dismissedValue) setDismissedValue(null);
1056
- const hasCommands = !!commands && commands.length > 0;
1461
+ const hasMenus = !!commands && commands.length > 0;
1057
1462
  const valueLooksLikeSlashCommand = value.startsWith("/") && !/\s/.test(value);
1058
- const commandsOpen = hasCommands && valueLooksLikeSlashCommand && value !== dismissedValue;
1059
- const slashFilter = commandsOpen ? value.slice(1) : "";
1463
+ const menuOpen = hasMenus && valueLooksLikeSlashCommand && value !== dismissedValue;
1464
+ const menuItems = commands ?? [];
1465
+ const menuFilter = value.slice(1);
1060
1466
  const handleSubmit = () => {
1061
1467
  if (isDisabled) return;
1062
1468
  if (status === "streaming") onAbort?.();
@@ -1070,19 +1476,19 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, dis
1070
1476
  const handleOnChange = (event) => {
1071
1477
  onValueChange?.(event.target.value);
1072
1478
  };
1073
- const handleCommandSelect = (commandValue) => {
1074
- onValueChange?.(commandValue + " ");
1479
+ const handleMenuSelect = (item) => {
1480
+ onValueChange?.(item.value + " ");
1075
1481
  textareaRef.current?.focus();
1076
1482
  };
1077
1483
  const handleOnKeyDown = (event) => {
1078
- if (commandsOpen) {
1484
+ if (menuOpen) {
1079
1485
  if (event.key === "Escape") {
1080
1486
  event.preventDefault();
1081
1487
  setDismissedValue(value);
1082
1488
  return;
1083
1489
  }
1084
1490
  if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter") return;
1085
- } else if (hasCommands && CMDK_HIJACKED_KEYS.has(event.key) && isCmdkNavigation(event)) event.stopPropagation();
1491
+ } else if (hasMenus && CMDK_HIJACKED_KEYS.has(event.key) && isCmdkNavigation(event)) event.stopPropagation();
1086
1492
  if (event.key === "Enter" && !event.shiftKey && !event.ctrlKey && !event.metaKey) {
1087
1493
  event.preventDefault();
1088
1494
  if (status !== "streaming") handleSubmit();
@@ -1100,48 +1506,63 @@ const PromptInput = ({ onSend, onAbort, onContinue, prefix, variant, suffix, dis
1100
1506
  "data-status": status,
1101
1507
  onMouseDown: handleOnMouseDown,
1102
1508
  onSubmit: handleOnSubmit,
1103
- className: cn(promptInputVariants({ variant }), disabled && "tw:bg-none tw:border-border tw:shadow-[inset_0_100vw_rgb(var(--muted))] tw:cursor-default"),
1509
+ className: cn(promptInputVariants({ variant }), disabled && "tw:bg-none tw:border-border tw:shadow-[inset_0_100vw_rgb(var(--muted))] tw:cursor-default", className),
1104
1510
  children: [
1105
- prefix && /* @__PURE__ */ jsx("div", {
1106
- className: "tw:flex tw:items-center",
1107
- children: prefix
1511
+ attachments && /* @__PURE__ */ jsx("div", {
1512
+ className: "tw:flex tw:flex-col tw:gap-2 tw:pb-2",
1513
+ children: attachments
1108
1514
  }),
1109
1515
  /* @__PURE__ */ jsx("label", {
1110
1516
  htmlFor: textareaId,
1111
1517
  className: "tw:sr-only",
1112
1518
  children: "Prompt Input"
1113
1519
  }),
1114
- /* @__PURE__ */ jsx(TextareaAutosize, {
1115
- ...textareaProps,
1116
- disabled,
1117
- minRows: minRows ?? defaultMinRows,
1118
- maxRows,
1119
- id: textareaId,
1120
- ref: setTextareaNode,
1121
- value,
1122
- onChange: handleOnChange,
1123
- onKeyDown: handleOnKeyDown,
1124
- className: cn("tw:resize-none", "tw:outline-hidden", "tw:w-full", "tw:px-2", "tw:bg-transparent", disabled ? "tw:text-muted-foreground" : "tw:text-foreground", "tw:text-sm", "tw:font-sans", "tw:transition-[width]", "tw:delay-75", "tw:ease-linear", "tw:overflow-auto")
1125
- }),
1126
1520
  /* @__PURE__ */ jsxs("div", {
1127
- className: cn("tw:flex tw:items-center tw:gap-4", variant === "inline" ? "tw:items-end" : "tw:items-center"),
1128
- children: [suffix, /* @__PURE__ */ jsx(PromptInputSubmit, {
1129
- disabled: isDisabled,
1130
- status
1131
- })]
1521
+ className: cn("tw:flex tw:min-w-0 tw:gap-2", variant === "inline" ? "tw:flex-row tw:items-center" : "tw:flex-col"),
1522
+ children: [
1523
+ prefix && /* @__PURE__ */ jsx("div", {
1524
+ className: "tw:flex tw:shrink-0 tw:items-center",
1525
+ children: prefix
1526
+ }),
1527
+ /* @__PURE__ */ jsx("div", {
1528
+ className: "tw:flex tw:min-w-0 tw:flex-1 tw:items-center",
1529
+ children: /* @__PURE__ */ jsx(TextareaAutosize, {
1530
+ ...textareaProps,
1531
+ disabled,
1532
+ minRows: minRows ?? defaultMinRows,
1533
+ maxRows,
1534
+ id: textareaId,
1535
+ ref: setTextareaNode,
1536
+ value,
1537
+ onChange: handleOnChange,
1538
+ onKeyDown: handleOnKeyDown,
1539
+ className: cn("tw:resize-none", "tw:outline-hidden", "tw:min-w-32 tw:flex-1", variant === "inline" ? "tw:px-0" : "tw:pl-2 tw:pr-0", "tw:bg-transparent", disabled ? "tw:text-muted-foreground" : "tw:text-foreground", "tw:text-sm", "tw:font-sans", "tw:transition-[width]", "tw:delay-75", "tw:ease-linear", "tw:overflow-auto")
1540
+ })
1541
+ }),
1542
+ /* @__PURE__ */ jsxs("div", {
1543
+ className: cn("tw:flex tw:shrink-0 tw:items-center tw:gap-1", "tw:[&>*:last-child]:ml-1", variant === "inline" && "tw:pr-2.5"),
1544
+ children: [suffix, /* @__PURE__ */ jsx(PromptInputSubmit, {
1545
+ disabled: isDisabled,
1546
+ status
1547
+ })]
1548
+ })
1549
+ ]
1132
1550
  })
1133
1551
  ]
1134
1552
  })
1135
1553
  });
1136
- return /* @__PURE__ */ jsx(TooltipProvider, { children: hasCommands ? /* @__PURE__ */ jsx(PromptInputCommands, {
1137
- items: commands ?? [],
1138
- open: commandsOpen,
1139
- onSelect: handleCommandSelect,
1140
- filterValue: slashFilter,
1554
+ return /* @__PURE__ */ jsx(TooltipProvider, { children: hasMenus ? /* @__PURE__ */ jsx(PromptInputCommands, {
1555
+ items: menuItems,
1556
+ open: menuOpen,
1557
+ onSelect: handleMenuSelect,
1558
+ filterValue: menuFilter,
1559
+ emptyText: "No commands found",
1560
+ onClose: () => setDismissedValue(value),
1141
1561
  children: formEl
1142
1562
  }) : formEl });
1143
1563
  };
1144
1564
  PromptInput.Action = PromptInputAction;
1565
+ PromptInput.MenuTrigger = PromptInputMenuTrigger;
1145
1566
  //#endregion
1146
1567
  //#region src/blocks/AIKit/Question.tsx
1147
1568
  const handleKeyDown = (callback) => (e) => {
@@ -1154,6 +1575,10 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1154
1575
  const [isCustomExpanded, setIsCustomExpanded] = useState(false);
1155
1576
  const isCustomSelected = isCustomExpanded || customAnswer.length > 0;
1156
1577
  const hasSteps = currentStep != null && totalSteps != null;
1578
+ const showStepControls = hasSteps && totalSteps > 1;
1579
+ const showSkipButton = showSkip && onSkip != null;
1580
+ const showSubmitButton = multiSelect || isCustomSelected;
1581
+ const showFooter = showStepControls || showSkipButton || showSubmitButton;
1157
1582
  const progressValue = hasSteps ? currentStep / totalSteps * 100 : 0;
1158
1583
  const isFirstStep = currentStep === 1;
1159
1584
  const isLastStep = hasSteps && currentStep === totalSteps;
@@ -1280,14 +1705,14 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1280
1705
  }),
1281
1706
  /* @__PURE__ */ jsxs("div", {
1282
1707
  className: "tw:flex tw:flex-col",
1283
- children: [hasSteps && /* @__PURE__ */ jsx(Progress, {
1708
+ children: [showStepControls && /* @__PURE__ */ jsx(Progress, {
1284
1709
  size: "sm",
1285
1710
  value: progressValue,
1286
1711
  className: "tw:rounded-none"
1287
- }), /* @__PURE__ */ jsxs("div", {
1288
- className: cn("tw:flex tw:items-center tw:justify-end tw:gap-4 tw:px-4 tw:py-3", !hasSteps && "tw:border-0 tw:border-t tw:border-solid tw:border-border"),
1712
+ }), showFooter && /* @__PURE__ */ jsxs("div", {
1713
+ className: cn("tw:flex tw:items-center tw:justify-end tw:gap-4 tw:px-4 tw:py-3", !showStepControls && "tw:border-0 tw:border-t tw:border-solid tw:border-border"),
1289
1714
  children: [
1290
- hasSteps && /* @__PURE__ */ jsxs("div", {
1715
+ showStepControls && /* @__PURE__ */ jsxs("div", {
1291
1716
  className: "tw:flex tw:items-center tw:gap-2",
1292
1717
  children: [
1293
1718
  /* @__PURE__ */ jsx("button", {
@@ -1316,14 +1741,14 @@ const Question = ({ question, header, options, multiSelect = true, selectedOptio
1316
1741
  })
1317
1742
  ]
1318
1743
  }),
1319
- showSkip && onSkip && /* @__PURE__ */ jsx(Button, {
1744
+ showSkipButton && /* @__PURE__ */ jsx(Button, {
1320
1745
  variant: "outline",
1321
1746
  size: "small",
1322
1747
  onClick: onSkip,
1323
1748
  "data-testid": "question-skip-button",
1324
1749
  children: "Skip"
1325
1750
  }),
1326
- (multiSelect || isCustomSelected) && (submitTooltip ? /* @__PURE__ */ jsx(Tooltip, {
1751
+ showSubmitButton && (submitTooltip ? /* @__PURE__ */ jsx(Tooltip, {
1327
1752
  tooltip: submitTooltip,
1328
1753
  placement: "top",
1329
1754
  triggerAsChild: true,
@@ -1656,6 +2081,27 @@ const formatPercentage = (value) => {
1656
2081
  return `${value < 100 ? Math.min(rounded, 99) : rounded}%`;
1657
2082
  };
1658
2083
  //#endregion
2084
+ //#region src/blocks/AIKit/UsageBar.tsx
2085
+ /**
2086
+ * One budget, as a labelled row over a bar.
2087
+ *
2088
+ * - The label and the figure sit on one baseline at either end of the row; the bar spans both.
2089
+ * - `value` over 100 or `NaN` is clamped by `clampPercentage` — `Progress`'s indicator is
2090
+ * unclamped and would otherwise slide off the row.
2091
+ */
2092
+ const UsageBar = ({ value, label, variant, className, ...props }) => /* @__PURE__ */ jsxs("span", {
2093
+ ...props,
2094
+ className: cn("tw:flex tw:flex-col tw:gap-1.5 tw:text-xs tw:text-muted-foreground", className),
2095
+ children: [/* @__PURE__ */ jsxs("span", {
2096
+ className: "tw:flex tw:items-baseline tw:justify-between tw:gap-2",
2097
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", { children: `${formatPercentage(Math.max(0, 100 - value))} left` })]
2098
+ }), /* @__PURE__ */ jsx(Progress, {
2099
+ size: "sm",
2100
+ variant,
2101
+ value: clampPercentage(value)
2102
+ })]
2103
+ });
2104
+ //#endregion
1659
2105
  //#region src/blocks/AIKit/UsageStatsRing.tsx
1660
2106
  const UsageStatsRing = ({ value, size = 16, strokeWidth = 2.5, className }) => {
1661
2107
  const radius = Math.max(0, (size - strokeWidth) / 2 - 1);
@@ -1698,9 +2144,10 @@ const UsageStats = ({ value, alert = false, children, className, ariaLabel, alig
1698
2144
  onOpenChange,
1699
2145
  children: [/* @__PURE__ */ jsx(HoverCard.Trigger, {
1700
2146
  asChild: true,
1701
- children: /* @__PURE__ */ jsxs("span", {
2147
+ children: /* @__PURE__ */ jsxs("button", {
2148
+ type: "button",
1702
2149
  "aria-label": ariaLabel ?? defaultLabel,
1703
- className: cn("tw:relative tw:inline-flex tw:size-8 tw:items-center tw:justify-center", className),
2150
+ className: cn("tw:relative tw:inline-flex tw:size-8 tw:shrink-0 tw:items-center tw:justify-center", "tw:cursor-pointer tw:rounded-full tw:border-0 tw:bg-transparent tw:p-0", "tw:transition-colors tw:hover:bg-muted", "tw:focus-visible:outline-hidden tw:focus-visible:ring-2 tw:focus-visible:ring-ring/50", className),
1704
2151
  children: [/* @__PURE__ */ jsx(UsageStatsRing, { value }), hasAlert && /* @__PURE__ */ jsx(AlertCircle, {
1705
2152
  "data-testid": "usage-stats-alert",
1706
2153
  className: cn("tw:absolute tw:-right-0.5 tw:-top-0.5 tw:size-3", isWarning ? "tw:text-warning-500" : "tw:text-error-500")
@@ -1715,6 +2162,6 @@ const UsageStats = ({ value, alert = false, children, className, ariaLabel, alig
1715
2162
  });
1716
2163
  };
1717
2164
  //#endregion
1718
- export { AnswerActions, AnswerSummary, CollapsibleStepHeader, DIAGRAM_LANGUAGES, InlineStepDetail, InlineStepLabel, Markdown, MarkdownTable, MessageBubble, PlanApproval, PlanRewritingIndicator, PromptInput, Question, QuestionBlock, SuggestedPrompt, ThinkingIndicator, ToolCallApproval, ToolCallChip, ToolCallStatusHeader, UsageStats, UsageStatsRing, clampPercentage, formatPercentage };
2165
+ export { AnswerActions, AnswerSummary, ChatInlineIcon, CollapsibleStepHeader, DIAGRAM_LANGUAGES, InlineStepDetail, InlineStepGroup, InlineStepLabel, Markdown, MarkdownTable, MessageBubble, PlanApproval, PlanRewritingIndicator, PromptInput, Question, QuestionBlock, SuggestedPrompt, ThinkingIndicator, ToolCallApproval, ToolCallChip, ToolCallStatusHeader, UsageBar, UsageStats, UsageStatsRing, clampPercentage, formatPercentage, getChatInlineIconName };
1719
2166
 
1720
- //# sourceMappingURL=AIKit-CQxVwyAk.js.map
2167
+ //# sourceMappingURL=AIKit-ELIsegJz.js.map