@gram-ai/elements 1.34.0 → 1.36.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 (238) hide show
  1. package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
  2. package/dist/compat-shims-DxtUrORi.js.map +1 -1
  3. package/dist/compat-shims.d.ts +9 -8
  4. package/dist/components/Chat/index.d.ts +2 -1
  5. package/dist/components/ChatHistory.d.ts +1 -1
  6. package/dist/components/FrontendTools/index.d.ts +1 -1
  7. package/dist/components/Replay.d.ts +1 -1
  8. package/dist/components/Replay.stories.d.ts +2 -2
  9. package/dist/components/ShadowRoot.d.ts +1 -1
  10. package/dist/components/ShareButton/index.d.ts +1 -1
  11. package/dist/components/assistant-ui/thinking-indicator.d.ts +8 -0
  12. package/dist/components/ui/avatar.d.ts +3 -3
  13. package/dist/components/ui/button.d.ts +2 -2
  14. package/dist/components/ui/buttonVariants.d.ts +2 -2
  15. package/dist/components/ui/calendar.d.ts +2 -1
  16. package/dist/components/ui/collapsible.d.ts +3 -3
  17. package/dist/components/ui/dialog.d.ts +10 -10
  18. package/dist/components/ui/popover.d.ts +4 -4
  19. package/dist/components/ui/skeleton.d.ts +1 -1
  20. package/dist/components/ui/time-range-picker.d.ts +2 -1
  21. package/dist/components/ui/tool-ui.d.ts +9 -7
  22. package/dist/components/ui/tooltip.d.ts +4 -4
  23. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  24. package/dist/contexts/ElementsProvider.d.ts +1 -1
  25. package/dist/contexts/ToolApprovalContext.d.ts +2 -2
  26. package/dist/contexts/ToolExecutionContext.d.ts +1 -1
  27. package/dist/contexts/portal-container.d.ts +1 -1
  28. package/dist/elements.cjs +1 -1
  29. package/dist/elements.css +1 -1
  30. package/dist/elements.js +2 -2
  31. package/dist/hooks/useDensity.d.ts +1 -1
  32. package/dist/hooks/useElements.d.ts +2 -1
  33. package/dist/hooks/useGramThreadListAdapter.d.ts +13 -0
  34. package/dist/hooks/useRadius.d.ts +1 -1
  35. package/dist/hooks/useThemeProps.d.ts +1 -1
  36. package/dist/hooks/useToolApproval.d.ts +2 -1
  37. package/dist/index-7fTI_vaV.cjs +194 -0
  38. package/dist/index-7fTI_vaV.cjs.map +1 -0
  39. package/dist/{index-B5lZrrO2.js → index-Bv-yE4G1.js} +2919 -2806
  40. package/dist/index-Bv-yE4G1.js.map +1 -0
  41. package/dist/{index-BFU6NvbL.js → index-BziIHO9O.js} +9621 -9308
  42. package/dist/index-BziIHO9O.js.map +1 -0
  43. package/dist/{index-C08dvTEo.cjs → index-CGBkMd0d.cjs} +48 -48
  44. package/dist/index-CGBkMd0d.cjs.map +1 -0
  45. package/dist/lib/errorTracking.d.ts +1 -1
  46. package/dist/lib/tools.d.ts +11 -10
  47. package/dist/plugins/generative-ui/catalog.d.ts +3 -3
  48. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  49. package/dist/plugins/generative-ui/ui/accordion.d.ts +4 -4
  50. package/dist/plugins/generative-ui/ui/action-button.d.ts +1 -1
  51. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +2 -1
  52. package/dist/plugins/generative-ui/ui/alert.d.ts +3 -3
  53. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +2 -1
  54. package/dist/plugins/generative-ui/ui/avatar.d.ts +6 -6
  55. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  56. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -1
  57. package/dist/plugins/generative-ui/ui/button.d.ts +3 -3
  58. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +1 -1
  59. package/dist/plugins/generative-ui/ui/card.d.ts +7 -7
  60. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +2 -1
  61. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  62. package/dist/plugins/generative-ui/ui/data-table.d.ts +1 -1
  63. package/dist/plugins/generative-ui/ui/dialog.d.ts +10 -10
  64. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +15 -15
  65. package/dist/plugins/generative-ui/ui/grid.d.ts +1 -1
  66. package/dist/plugins/generative-ui/ui/index.d.ts +57 -40
  67. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +2 -1
  68. package/dist/plugins/generative-ui/ui/input.d.ts +1 -1
  69. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  70. package/dist/plugins/generative-ui/ui/list.d.ts +2 -1
  71. package/dist/plugins/generative-ui/ui/metric.d.ts +1 -1
  72. package/dist/plugins/generative-ui/ui/pagination.d.ts +7 -7
  73. package/dist/plugins/generative-ui/ui/popover.d.ts +7 -7
  74. package/dist/plugins/generative-ui/ui/progress.d.ts +1 -1
  75. package/dist/plugins/generative-ui/ui/radio-group.d.ts +2 -2
  76. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +2 -1
  77. package/dist/plugins/generative-ui/ui/select.d.ts +10 -10
  78. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  79. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +2 -1
  80. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  81. package/dist/plugins/generative-ui/ui/stack.d.ts +1 -1
  82. package/dist/plugins/generative-ui/ui/switch.d.ts +1 -1
  83. package/dist/plugins/generative-ui/ui/table.d.ts +8 -8
  84. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +2 -2
  85. package/dist/plugins/generative-ui/ui/tabs.d.ts +4 -4
  86. package/dist/plugins/generative-ui/ui/text.d.ts +1 -1
  87. package/dist/plugins/generative-ui/ui/textarea.d.ts +1 -1
  88. package/dist/plugins/generative-ui/ui/tooltip.d.ts +4 -4
  89. package/dist/plugins.cjs +1 -1
  90. package/dist/plugins.js +1 -1
  91. package/dist/{profiler-BRnyr1GA.cjs → profiler-DuJEf_S6.cjs} +2 -2
  92. package/dist/{profiler-BRnyr1GA.cjs.map → profiler-DuJEf_S6.cjs.map} +1 -1
  93. package/dist/{profiler-KLSTpp6I.js → profiler-xLXOPfmj.js} +2 -2
  94. package/dist/{profiler-KLSTpp6I.js.map → profiler-xLXOPfmj.js.map} +1 -1
  95. package/dist/react-shim.cjs.map +1 -1
  96. package/dist/react-shim.d.ts +1 -1
  97. package/dist/react-shim.js +1 -4
  98. package/dist/react-shim.js.map +1 -1
  99. package/dist/server/bun.cjs.map +1 -1
  100. package/dist/server/bun.js.map +1 -1
  101. package/dist/server/express.cjs.map +1 -1
  102. package/dist/server/express.js.map +1 -1
  103. package/dist/server/fastify.cjs.map +1 -1
  104. package/dist/server/fastify.js.map +1 -1
  105. package/dist/server/hono.cjs.map +1 -1
  106. package/dist/server/hono.js.map +1 -1
  107. package/dist/server/nextjs.cjs.map +1 -1
  108. package/dist/server/nextjs.js.map +1 -1
  109. package/dist/server/tanstack-start.cjs.map +1 -1
  110. package/dist/server/tanstack-start.js.map +1 -1
  111. package/dist/{startRecording-CKx-YWbq.cjs → startRecording-C2XF9-Ol.cjs} +2 -2
  112. package/dist/{startRecording-CKx-YWbq.cjs.map → startRecording-C2XF9-Ol.cjs.map} +1 -1
  113. package/dist/{startRecording-BfxB1xxR.js → startRecording-qKnXr4lw.js} +2 -2
  114. package/dist/{startRecording-BfxB1xxR.js.map → startRecording-qKnXr4lw.js.map} +1 -1
  115. package/dist/types/index.d.ts +29 -3
  116. package/package.json +8 -11
  117. package/src/compat-shims.ts +16 -2
  118. package/src/components/Chat/index.tsx +4 -1
  119. package/src/components/Chat/stories/FrontendTools.stories.tsx +1 -1
  120. package/src/components/Chat/stories/ToolApproval.stories.tsx +2 -2
  121. package/src/components/Chat/stories/Tools.stories.tsx +13 -5
  122. package/src/components/ChatHistory.tsx +3 -1
  123. package/src/components/FrontendTools/index.tsx +1 -1
  124. package/src/components/MessageContent.tsx +1 -0
  125. package/src/components/Replay.stories.tsx +2 -3
  126. package/src/components/Replay.tsx +17 -10
  127. package/src/components/ShadowRoot.tsx +2 -2
  128. package/src/components/ShareButton/index.tsx +4 -2
  129. package/src/components/assistant-ui/assistant-modal.tsx +5 -3
  130. package/src/components/assistant-ui/attachment.tsx +1 -1
  131. package/src/components/assistant-ui/error-boundary.tsx +1 -1
  132. package/src/components/assistant-ui/markdown-text.tsx +1 -1
  133. package/src/components/assistant-ui/thinking-indicator.tsx +175 -0
  134. package/src/components/assistant-ui/thread.tsx +251 -27
  135. package/src/components/assistant-ui/tool-fallback.tsx +8 -8
  136. package/src/components/assistant-ui/tool-group.tsx +4 -13
  137. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +1 -1
  138. package/src/components/ui/avatar.tsx +3 -3
  139. package/src/components/ui/calendar.tsx +1 -1
  140. package/src/components/ui/collapsible.tsx +7 -3
  141. package/src/components/ui/dialog.tsx +18 -10
  142. package/src/components/ui/generative-ui.tsx +9 -4
  143. package/src/components/ui/popover.tsx +4 -4
  144. package/src/components/ui/skeleton.tsx +4 -1
  145. package/src/components/ui/time-range-picker.stories.tsx +164 -154
  146. package/src/components/ui/time-range-picker.tsx +11 -5
  147. package/src/components/ui/tool-ui.tsx +68 -40
  148. package/src/components/ui/tooltip.tsx +4 -4
  149. package/src/contexts/ChatIdContext.tsx +1 -1
  150. package/src/contexts/ConnectionStatusContext.tsx +6 -5
  151. package/src/contexts/ElementsProvider.tsx +64 -41
  152. package/src/contexts/ReplayContext.ts +1 -1
  153. package/src/contexts/ToolApprovalContext.tsx +5 -1
  154. package/src/contexts/ToolExecutionContext.tsx +1 -1
  155. package/src/contexts/portal-container.tsx +1 -1
  156. package/src/global.css +55 -16
  157. package/src/hooks/useAuth.ts +2 -1
  158. package/src/hooks/useDensity.ts +1 -1
  159. package/src/hooks/useElements.ts +2 -1
  160. package/src/hooks/useFollowOnSuggestions.ts +3 -6
  161. package/src/hooks/useGramThreadListAdapter.tsx +50 -3
  162. package/src/hooks/useMCPTools.ts +2 -2
  163. package/src/hooks/useModel.ts +1 -3
  164. package/src/hooks/usePluginComponents.ts +3 -1
  165. package/src/hooks/useRadius.ts +1 -1
  166. package/src/hooks/useSession.ts +3 -1
  167. package/src/hooks/useThemeProps.ts +5 -5
  168. package/src/hooks/useToolApproval.ts +2 -1
  169. package/src/lib/cassette.ts +20 -8
  170. package/src/lib/errorTracking.ts +1 -4
  171. package/src/lib/messageConverter.test.ts +11 -13
  172. package/src/lib/messageConverter.ts +13 -4
  173. package/src/lib/token.ts +2 -5
  174. package/src/lib/tool-mentions.ts +5 -2
  175. package/src/lib/tools.byte-cap.test.ts +1 -1
  176. package/src/lib/tools.test.ts +1 -1
  177. package/src/lib/tools.ts +15 -5
  178. package/src/lib/utils.ts +2 -2
  179. package/src/lib.d.ts +8 -1
  180. package/src/plugins/chart/chart.test.ts +3 -4
  181. package/src/plugins/chart/component.tsx +7 -6
  182. package/src/plugins/chart/ui/area-chart.tsx +1 -1
  183. package/src/plugins/chart/ui/line-chart.tsx +1 -1
  184. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +2 -2
  185. package/src/plugins/generative-ui/ui/accordion.tsx +4 -4
  186. package/src/plugins/generative-ui/ui/action-button.tsx +4 -2
  187. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +1 -1
  188. package/src/plugins/generative-ui/ui/alert.tsx +7 -3
  189. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +5 -1
  190. package/src/plugins/generative-ui/ui/avatar.tsx +12 -6
  191. package/src/plugins/generative-ui/ui/badge.tsx +1 -1
  192. package/src/plugins/generative-ui/ui/button-wrapper.tsx +1 -1
  193. package/src/plugins/generative-ui/ui/button.tsx +1 -1
  194. package/src/plugins/generative-ui/ui/card-wrapper.tsx +1 -1
  195. package/src/plugins/generative-ui/ui/card.tsx +28 -7
  196. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +1 -1
  197. package/src/plugins/generative-ui/ui/checkbox.tsx +1 -1
  198. package/src/plugins/generative-ui/ui/data-table.tsx +1 -1
  199. package/src/plugins/generative-ui/ui/dialog.tsx +15 -10
  200. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +33 -15
  201. package/src/plugins/generative-ui/ui/grid.tsx +1 -1
  202. package/src/plugins/generative-ui/ui/index.ts +154 -40
  203. package/src/plugins/generative-ui/ui/input-wrapper.tsx +1 -1
  204. package/src/plugins/generative-ui/ui/input.tsx +5 -1
  205. package/src/plugins/generative-ui/ui/label.tsx +1 -1
  206. package/src/plugins/generative-ui/ui/list.tsx +5 -1
  207. package/src/plugins/generative-ui/ui/metric.tsx +2 -1
  208. package/src/plugins/generative-ui/ui/pagination.tsx +12 -7
  209. package/src/plugins/generative-ui/ui/popover.tsx +13 -7
  210. package/src/plugins/generative-ui/ui/progress.tsx +1 -1
  211. package/src/plugins/generative-ui/ui/radio-group.tsx +2 -2
  212. package/src/plugins/generative-ui/ui/select-wrapper.tsx +1 -1
  213. package/src/plugins/generative-ui/ui/select.tsx +14 -10
  214. package/src/plugins/generative-ui/ui/separator.tsx +1 -1
  215. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +1 -1
  216. package/src/plugins/generative-ui/ui/skeleton.tsx +4 -1
  217. package/src/plugins/generative-ui/ui/stack.tsx +1 -1
  218. package/src/plugins/generative-ui/ui/switch.tsx +1 -1
  219. package/src/plugins/generative-ui/ui/table.tsx +29 -8
  220. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +5 -2
  221. package/src/plugins/generative-ui/ui/tabs.tsx +4 -4
  222. package/src/plugins/generative-ui/ui/text.tsx +1 -1
  223. package/src/plugins/generative-ui/ui/textarea.tsx +4 -1
  224. package/src/plugins/generative-ui/ui/tooltip.tsx +4 -4
  225. package/src/react-shim.ts +9 -4
  226. package/src/server/bun.ts +1 -1
  227. package/src/server/express.ts +1 -1
  228. package/src/server/fastify.ts +1 -1
  229. package/src/server/hono.ts +1 -1
  230. package/src/server/nextjs.ts +1 -1
  231. package/src/server/tanstack-start.ts +1 -1
  232. package/src/storybook.d.ts +5 -0
  233. package/src/types/index.ts +39 -3
  234. package/dist/index-B5lZrrO2.js.map +0 -1
  235. package/dist/index-BFU6NvbL.js.map +0 -1
  236. package/dist/index-C08dvTEo.cjs.map +0 -1
  237. package/dist/index-DzZ1-jQY.cjs +0 -194
  238. package/dist/index-DzZ1-jQY.cjs.map +0 -1
@@ -36,171 +36,214 @@ const meta: Meta<typeof TimeRangePicker> = {
36
36
  export default meta;
37
37
  type Story = StoryObj<typeof TimeRangePicker>;
38
38
 
39
+ function DefaultStory() {
40
+ const [preset, setPreset] = useState<DateRangePreset | null>("7d");
41
+ const [customRange, setCustomRange] = useState<TimeRange | null>(null);
42
+
43
+ return (
44
+ <TimeRangePicker
45
+ preset={customRange ? null : preset}
46
+ customRange={customRange}
47
+ onPresetChange={(p) => {
48
+ setPreset(p);
49
+ setCustomRange(null);
50
+ }}
51
+ onCustomRangeChange={(from, to, label) => {
52
+ setCustomRange({ from, to });
53
+ setPreset(null);
54
+ console.log("Custom range:", { from, to, label });
55
+ }}
56
+ onClearCustomRange={() => {
57
+ setCustomRange(null);
58
+ setPreset("7d");
59
+ }}
60
+ />
61
+ );
62
+ }
63
+
39
64
  /**
40
65
  * Default time range picker with preset badges and calendar.
41
66
  * Supports natural language input with AI parsing.
42
67
  */
43
68
  export const Default: Story = {
44
- render: () => {
45
- const [preset, setPreset] = useState<DateRangePreset | null>("7d");
46
- const [customRange, setCustomRange] = useState<TimeRange | null>(null);
47
-
48
- return (
49
- <TimeRangePicker
50
- preset={customRange ? null : preset}
51
- customRange={customRange}
52
- onPresetChange={(p) => {
53
- setPreset(p);
54
- setCustomRange(null);
55
- }}
56
- onCustomRangeChange={(from, to, label) => {
57
- setCustomRange({ from, to });
58
- setPreset(null);
59
- console.log("Custom range:", { from, to, label });
60
- }}
61
- onClearCustomRange={() => {
62
- setCustomRange(null);
63
- setPreset("7d");
64
- }}
65
- />
66
- );
67
- },
69
+ render: () => <DefaultStory />,
68
70
  };
69
71
 
72
+ function WithTimezoneStory() {
73
+ const [preset, setPreset] = useState<DateRangePreset | null>("30d");
74
+ const [customRange, setCustomRange] = useState<TimeRange | null>(null);
75
+
76
+ return (
77
+ <TimeRangePicker
78
+ preset={customRange ? null : preset}
79
+ customRange={customRange}
80
+ onPresetChange={(p) => {
81
+ setPreset(p);
82
+ setCustomRange(null);
83
+ }}
84
+ onCustomRangeChange={(from, to) => {
85
+ setCustomRange({ from, to });
86
+ setPreset(null);
87
+ }}
88
+ onClearCustomRange={() => {
89
+ setCustomRange(null);
90
+ setPreset("30d");
91
+ }}
92
+ timezone="UTC-08:00"
93
+ />
94
+ );
95
+ }
96
+
70
97
  /**
71
98
  * Time range picker with timezone indicator.
72
99
  */
73
100
  export const WithTimezone: Story = {
74
- render: () => {
75
- const [preset, setPreset] = useState<DateRangePreset | null>("30d");
76
- const [customRange, setCustomRange] = useState<TimeRange | null>(null);
101
+ render: () => <WithTimezoneStory />,
102
+ };
103
+
104
+ function WithLiveModeStory() {
105
+ const [preset, setPreset] = useState<DateRangePreset | null>("15m");
106
+ const [customRange, setCustomRange] = useState<TimeRange | null>(null);
107
+ const [isLive, setIsLive] = useState(true);
108
+
109
+ return (
110
+ <TimeRangePicker
111
+ preset={customRange ? null : preset}
112
+ customRange={customRange}
113
+ onPresetChange={(p) => {
114
+ setPreset(p);
115
+ setCustomRange(null);
116
+ }}
117
+ onCustomRangeChange={(from, to) => {
118
+ setCustomRange({ from, to });
119
+ setPreset(null);
120
+ }}
121
+ onClearCustomRange={() => {
122
+ setCustomRange(null);
123
+ setPreset("15m");
124
+ }}
125
+ showLive
126
+ isLive={isLive}
127
+ onLiveChange={setIsLive}
128
+ />
129
+ );
130
+ }
77
131
 
78
- return (
132
+ /**
133
+ * With LIVE mode toggle enabled.
134
+ */
135
+ export const WithLiveMode: Story = {
136
+ render: () => <WithLiveModeStory />,
137
+ };
138
+
139
+ /**
140
+ * Disabled state.
141
+ */
142
+ export const Disabled: Story = {
143
+ args: {
144
+ preset: "7d",
145
+ disabled: true,
146
+ },
147
+ };
148
+
149
+ function DatadogStyleStory() {
150
+ const [preset, setPreset] = useState<DateRangePreset | null>("7d");
151
+ const [customRange, setCustomRange] = useState<TimeRange | null>(null);
152
+ const [customLabel, setCustomLabel] = useState<string | null>(null);
153
+ const [isLive, setIsLive] = useState(false);
154
+
155
+ return (
156
+ <div className="space-y-4">
79
157
  <TimeRangePicker
80
158
  preset={customRange ? null : preset}
81
159
  customRange={customRange}
160
+ customRangeLabel={customLabel}
82
161
  onPresetChange={(p) => {
83
162
  setPreset(p);
84
163
  setCustomRange(null);
164
+ setCustomLabel(null);
85
165
  }}
86
- onCustomRangeChange={(from, to) => {
166
+ onCustomRangeChange={(from, to, label) => {
87
167
  setCustomRange({ from, to });
88
168
  setPreset(null);
169
+ setCustomLabel(label || null);
89
170
  }}
90
171
  onClearCustomRange={() => {
91
172
  setCustomRange(null);
92
- setPreset("30d");
173
+ setPreset("7d");
174
+ setCustomLabel(null);
93
175
  }}
176
+ showLive
177
+ isLive={isLive}
178
+ onLiveChange={setIsLive}
94
179
  timezone="UTC-08:00"
95
180
  />
96
- );
97
- },
98
- };
181
+ <div className="rounded-md bg-muted p-3 text-xs text-muted-foreground">
182
+ <strong>Current state:</strong>
183
+ <pre className="mt-1 overflow-auto">
184
+ {JSON.stringify(
185
+ {
186
+ preset,
187
+ customRange: customRange
188
+ ? {
189
+ from: customRange.from.toISOString(),
190
+ to: customRange.to.toISOString(),
191
+ }
192
+ : null,
193
+ customLabel,
194
+ isLive,
195
+ },
196
+ null,
197
+ 2,
198
+ )}
199
+ </pre>
200
+ </div>
201
+ </div>
202
+ );
203
+ }
99
204
 
100
205
  /**
101
- * With LIVE mode toggle enabled.
206
+ * Full Datadog-style configuration with all features.
207
+ * Type natural language like "3 days ago", "last Wednesday", "past 2 weeks".
102
208
  */
103
- export const WithLiveMode: Story = {
104
- render: () => {
105
- const [preset, setPreset] = useState<DateRangePreset | null>("15m");
106
- const [customRange, setCustomRange] = useState<TimeRange | null>(null);
107
- const [isLive, setIsLive] = useState(true);
209
+ export const DatadogStyle: Story = {
210
+ render: () => <DatadogStyleStory />,
211
+ };
108
212
 
109
- return (
213
+ function NaturalLanguageParsingStory() {
214
+ const [preset, setPreset] = useState<DateRangePreset | null>("30d");
215
+ const [customRange, setCustomRange] = useState<TimeRange | null>(null);
216
+ const [customLabel, setCustomLabel] = useState<string | null>(null);
217
+
218
+ return (
219
+ <div className="space-y-4">
220
+ <p className="text-sm text-muted-foreground">
221
+ Try typing: "yesterday", "3 days ago", "last Wednesday", "January"
222
+ </p>
110
223
  <TimeRangePicker
111
224
  preset={customRange ? null : preset}
112
225
  customRange={customRange}
226
+ customRangeLabel={customLabel}
113
227
  onPresetChange={(p) => {
114
228
  setPreset(p);
115
229
  setCustomRange(null);
230
+ setCustomLabel(null);
116
231
  }}
117
- onCustomRangeChange={(from, to) => {
232
+ onCustomRangeChange={(from, to, label) => {
118
233
  setCustomRange({ from, to });
119
234
  setPreset(null);
235
+ setCustomLabel(label || null);
236
+ console.log("AI parsed:", { from, to, label });
120
237
  }}
121
238
  onClearCustomRange={() => {
122
239
  setCustomRange(null);
123
- setPreset("15m");
240
+ setPreset("30d");
241
+ setCustomLabel(null);
124
242
  }}
125
- showLive
126
- isLive={isLive}
127
- onLiveChange={setIsLive}
128
243
  />
129
- );
130
- },
131
- };
132
-
133
- /**
134
- * Disabled state.
135
- */
136
- export const Disabled: Story = {
137
- args: {
138
- preset: "7d",
139
- disabled: true,
140
- },
141
- };
142
-
143
- /**
144
- * Full Datadog-style configuration with all features.
145
- * Type natural language like "3 days ago", "last Wednesday", "past 2 weeks".
146
- */
147
- export const DatadogStyle: Story = {
148
- render: () => {
149
- const [preset, setPreset] = useState<DateRangePreset | null>("7d");
150
- const [customRange, setCustomRange] = useState<TimeRange | null>(null);
151
- const [customLabel, setCustomLabel] = useState<string | null>(null);
152
- const [isLive, setIsLive] = useState(false);
153
-
154
- return (
155
- <div className="space-y-4">
156
- <TimeRangePicker
157
- preset={customRange ? null : preset}
158
- customRange={customRange}
159
- customRangeLabel={customLabel}
160
- onPresetChange={(p) => {
161
- setPreset(p);
162
- setCustomRange(null);
163
- setCustomLabel(null);
164
- }}
165
- onCustomRangeChange={(from, to, label) => {
166
- setCustomRange({ from, to });
167
- setPreset(null);
168
- setCustomLabel(label || null);
169
- }}
170
- onClearCustomRange={() => {
171
- setCustomRange(null);
172
- setPreset("7d");
173
- setCustomLabel(null);
174
- }}
175
- showLive
176
- isLive={isLive}
177
- onLiveChange={setIsLive}
178
- timezone="UTC-08:00"
179
- />
180
- <div className="rounded-md bg-muted p-3 text-xs text-muted-foreground">
181
- <strong>Current state:</strong>
182
- <pre className="mt-1 overflow-auto">
183
- {JSON.stringify(
184
- {
185
- preset,
186
- customRange: customRange
187
- ? {
188
- from: customRange.from.toISOString(),
189
- to: customRange.to.toISOString(),
190
- }
191
- : null,
192
- customLabel,
193
- isLive,
194
- },
195
- null,
196
- 2,
197
- )}
198
- </pre>
199
- </div>
200
- </div>
201
- );
202
- },
203
- };
244
+ </div>
245
+ );
246
+ }
204
247
 
205
248
  /**
206
249
  * Natural language parsing demo.
@@ -212,38 +255,5 @@ export const DatadogStyle: Story = {
212
255
  * - "January 2024"
213
256
  */
214
257
  export const NaturalLanguageParsing: Story = {
215
- render: () => {
216
- const [preset, setPreset] = useState<DateRangePreset | null>("30d");
217
- const [customRange, setCustomRange] = useState<TimeRange | null>(null);
218
- const [customLabel, setCustomLabel] = useState<string | null>(null);
219
-
220
- return (
221
- <div className="space-y-4">
222
- <p className="text-sm text-muted-foreground">
223
- Try typing: "yesterday", "3 days ago", "last Wednesday", "January"
224
- </p>
225
- <TimeRangePicker
226
- preset={customRange ? null : preset}
227
- customRange={customRange}
228
- customRangeLabel={customLabel}
229
- onPresetChange={(p) => {
230
- setPreset(p);
231
- setCustomRange(null);
232
- setCustomLabel(null);
233
- }}
234
- onCustomRangeChange={(from, to, label) => {
235
- setCustomRange({ from, to });
236
- setPreset(null);
237
- setCustomLabel(label || null);
238
- console.log("AI parsed:", { from, to, label });
239
- }}
240
- onClearCustomRange={() => {
241
- setCustomRange(null);
242
- setPreset("30d");
243
- setCustomLabel(null);
244
- }}
245
- />
246
- </div>
247
- );
248
- },
258
+ render: () => <NaturalLanguageParsingStory />,
249
259
  };
@@ -155,7 +155,7 @@ const BADGE_WIDTH = "min-w-10";
155
155
 
156
156
  export function getPresetRange(preset: DateRangePreset): TimeRange {
157
157
  const p = PRESETS.find((p) => p.value === preset);
158
- return p ? p.getRange() : PRESETS[5].getRange(); // Default to 3d
158
+ return p ? p.getRange() : PRESETS[5]!.getRange(); // Default to 3d
159
159
  }
160
160
 
161
161
  function getPresetByValue(value: DateRangePreset): TimeRangePreset | undefined {
@@ -188,11 +188,15 @@ function parseAsLocalDate(isoString: string): Date {
188
188
  // Try to extract just the date part and create a local date
189
189
  const dateMatch = isoString.match(/^(\d{4})-(\d{2})-(\d{2})/);
190
190
  if (dateMatch) {
191
- const [, year, month, day] = dateMatch;
191
+ const year = dateMatch[1]!;
192
+ const month = dateMatch[2]!;
193
+ const day = dateMatch[3]!;
192
194
  // Check if there's a time component
193
195
  const timeMatch = isoString.match(/T(\d{2}):(\d{2}):?(\d{2})?/);
194
196
  if (timeMatch) {
195
- const [, hours, minutes, seconds = "0"] = timeMatch;
197
+ const hours = timeMatch[1]!;
198
+ const minutes = timeMatch[2]!;
199
+ const seconds = timeMatch[3] ?? "0";
196
200
  return new Date(
197
201
  parseInt(year),
198
202
  parseInt(month) - 1,
@@ -372,7 +376,7 @@ function TimeRangePicker({
372
376
  projectSlug,
373
377
  authHeaders,
374
378
  className,
375
- }: TimeRangePickerProps) {
379
+ }: TimeRangePickerProps): React.JSX.Element {
376
380
  const [isOpen, setIsOpen] = React.useState(false);
377
381
  const [showCalendar, setShowCalendar] = React.useState(false);
378
382
  const [inputValue, setInputValue] = React.useState("");
@@ -585,7 +589,9 @@ function TimeRangePicker({
585
589
  onClick={handleInputClick}
586
590
  onFocus={handleInputFocus}
587
591
  onBlur={handleInputBlur}
588
- onKeyDown={handleInputKeyDown}
592
+ onKeyDown={(e) => {
593
+ void handleInputKeyDown(e);
594
+ }}
589
595
  placeholder="e.g., 3 days ago, last week..."
590
596
  disabled={disabled}
591
597
  className={cn(
@@ -159,7 +159,11 @@ function isStructuredContent(
159
159
  * Helper Components
160
160
  * -------------------------------------------------------------------------- */
161
161
 
162
- function StatusIndicator({ status }: { status: ToolStatus }) {
162
+ function StatusIndicator({
163
+ status,
164
+ }: {
165
+ status: ToolStatus;
166
+ }): React.JSX.Element {
163
167
  return (
164
168
  <div className={cn(statusVariants({ status }))}>
165
169
  {status === "pending" && null}
@@ -173,7 +177,7 @@ function StatusIndicator({ status }: { status: ToolStatus }) {
173
177
  );
174
178
  }
175
179
 
176
- function CopyButton({ content }: { content: string }) {
180
+ function CopyButton({ content }: { content: string }): React.JSX.Element {
177
181
  const [copied, setCopied] = useState(false);
178
182
 
179
183
  const handleCopy = async (e: React.MouseEvent) => {
@@ -185,7 +189,9 @@ function CopyButton({ content }: { content: string }) {
185
189
 
186
190
  return (
187
191
  <button
188
- onClick={handleCopy}
192
+ onClick={(e) => {
193
+ void handleCopy(e);
194
+ }}
189
195
  className="rounded p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
190
196
  aria-label="Copy to clipboard"
191
197
  >
@@ -226,7 +232,7 @@ function SyntaxHighlightedCode({
226
232
  text: string;
227
233
  language?: BundledLanguage;
228
234
  className?: string;
229
- }) {
235
+ }): React.JSX.Element {
230
236
  const [highlightedCode, setHighlightedCode] = useState<string | null>(null);
231
237
  const [expanded, setExpanded] = useState(false);
232
238
 
@@ -241,7 +247,7 @@ function SyntaxHighlightedCode({
241
247
  setHighlightedCode(null);
242
248
  if (!language || !canHighlight) return;
243
249
  let cancelled = false;
244
- codeToHtml(displayText, {
250
+ void codeToHtml(displayText, {
245
251
  lang: language,
246
252
  theme: "github-dark-default",
247
253
  rootStyle: "background-color: transparent;",
@@ -370,7 +376,7 @@ function ToolUISection({
370
376
  defaultExpanded = false,
371
377
  highlightSyntax = true,
372
378
  language = "json",
373
- }: ToolUISectionProps) {
379
+ }: ToolUISectionProps): React.JSX.Element {
374
380
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
375
381
 
376
382
  // For structured content, we don't stringify it
@@ -434,7 +440,7 @@ function ToolUI({
434
440
  onApproveOnce,
435
441
  onApproveForSession,
436
442
  onDeny,
437
- }: ToolUIProps) {
443
+ }: ToolUIProps): React.JSX.Element {
438
444
  // Use annotation title if available, otherwise fall back to name
439
445
  const displayName = annotations?.title || name;
440
446
  const isApprovalPending =
@@ -455,6 +461,7 @@ function ToolUI({
455
461
  if (!isApprovalPending && isExpanded && !defaultExpanded) {
456
462
  setIsExpanded(false);
457
463
  }
464
+ // oxlint-disable-next-line react-hooks/exhaustive-deps -- only react to approval transition; defaultExpanded/isExpanded are intentionally not deps
458
465
  }, [isApprovalPending]);
459
466
 
460
467
  // Handle approve based on selected mode
@@ -497,7 +504,9 @@ function ToolUI({
497
504
 
498
505
  {/* Tool row */}
499
506
  <button
500
- onClick={() => hasContent && setIsExpanded(!isExpanded)}
507
+ onClick={() => {
508
+ if (hasContent) setIsExpanded(!isExpanded);
509
+ }}
501
510
  disabled={!hasContent}
502
511
  className={cn(
503
512
  "flex w-full items-center gap-2 px-4 py-3 text-left",
@@ -702,6 +711,8 @@ interface ToolUIGroupProps {
702
711
  status?: "running" | "complete";
703
712
  /** Whether the group starts expanded */
704
713
  defaultExpanded?: boolean;
714
+ /** Render without the group header, showing children directly. */
715
+ headerless?: boolean;
705
716
  /** Child tool UI components */
706
717
  children: React.ReactNode;
707
718
  /** Additional class names */
@@ -713,11 +724,23 @@ function ToolUIGroup({
713
724
  icon,
714
725
  status = "complete",
715
726
  defaultExpanded = false,
727
+ headerless = false,
716
728
  children,
717
729
  className,
718
- }: ToolUIGroupProps) {
730
+ }: ToolUIGroupProps): React.JSX.Element {
719
731
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
720
732
 
733
+ // A headerless group shows its children unconditionally; when it gains a
734
+ // header mid-stream, start expanded — collapsing would hide content the
735
+ // user was already looking at.
736
+ const [prevHeaderless, setPrevHeaderless] = useState(headerless);
737
+ if (prevHeaderless !== headerless) {
738
+ setPrevHeaderless(headerless);
739
+ if (prevHeaderless) setIsExpanded(true);
740
+ }
741
+
742
+ const showChildren = headerless || isExpanded;
743
+
721
744
  return (
722
745
  <div
723
746
  data-slot="tool-ui-group"
@@ -727,40 +750,45 @@ function ToolUIGroup({
727
750
  )}
728
751
  >
729
752
  {/* Group header */}
730
- <button
731
- onClick={() => setIsExpanded(!isExpanded)}
732
- className="flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-accent/50"
733
- >
734
- {icon || (
735
- <StatusIndicator
736
- status={status === "running" ? "running" : "complete"}
737
- />
738
- )}
739
- <span
740
- className={cn(
741
- "flex-1 text-sm font-medium",
742
- status === "running" && "shimmer",
743
- )}
753
+ {!headerless && (
754
+ <button
755
+ onClick={() => setIsExpanded(!isExpanded)}
756
+ aria-expanded={isExpanded}
757
+ className="flex w-full items-center gap-2 px-4 py-3 text-left transition-colors hover:bg-accent/50"
744
758
  >
745
- {title}
746
- </span>
747
- <ChevronDownIcon
748
- className={cn(
749
- "size-4 text-muted-foreground transition-transform duration-200",
750
- isExpanded && "rotate-180",
759
+ {icon || (
760
+ <StatusIndicator
761
+ status={status === "running" ? "running" : "complete"}
762
+ />
751
763
  )}
752
- />
753
- </button>
754
-
755
- {/* Expandable children */}
756
- {isExpanded && (
757
- <div
758
- data-slot="tool-ui-group-content"
759
- className="border-t border-border"
760
- >
761
- {children}
762
- </div>
764
+ <span
765
+ className={cn(
766
+ "flex-1 text-sm font-medium",
767
+ status === "running" && "shimmer",
768
+ )}
769
+ >
770
+ {title}
771
+ </span>
772
+ <ChevronDownIcon
773
+ className={cn(
774
+ "size-4 text-muted-foreground transition-transform duration-200",
775
+ isExpanded && "rotate-180",
776
+ )}
777
+ />
778
+ </button>
763
779
  )}
780
+
781
+ {/* Collapsed children are hidden, not unmounted — unmounting would
782
+ reset their state (expansion, async syntax highlighting). */}
783
+ <div
784
+ data-slot="tool-ui-group-content"
785
+ className={cn(
786
+ !headerless && "border-t border-border",
787
+ !showChildren && "hidden",
788
+ )}
789
+ >
790
+ {children}
791
+ </div>
764
792
  </div>
765
793
  );
766
794
  }
@@ -9,7 +9,7 @@ import { usePortalContainer } from "@/hooks/usePortalContainer";
9
9
  function TooltipProvider({
10
10
  delayDuration = 0,
11
11
  ...props
12
- }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>): React.JSX.Element {
13
13
  return (
14
14
  <TooltipPrimitive.Provider
15
15
  data-slot="tooltip-provider"
@@ -21,7 +21,7 @@ function TooltipProvider({
21
21
 
22
22
  function Tooltip({
23
23
  ...props
24
- }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>): React.JSX.Element {
25
25
  return (
26
26
  <TooltipProvider>
27
27
  <TooltipPrimitive.Root data-slot="tooltip" {...props} />
@@ -31,7 +31,7 @@ function Tooltip({
31
31
 
32
32
  function TooltipTrigger({
33
33
  ...props
34
- }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
34
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>): React.JSX.Element {
35
35
  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
36
36
  }
37
37
 
@@ -43,7 +43,7 @@ function TooltipContent({
43
43
  ...props
44
44
  }: React.ComponentProps<typeof TooltipPrimitive.Content> & {
45
45
  container?: HTMLElement | null;
46
- }) {
46
+ }): React.JSX.Element {
47
47
  const portalContainer = usePortalContainer();
48
48
  return (
49
49
  <TooltipPrimitive.Portal container={container ?? portalContainer}>
@@ -12,7 +12,7 @@ export const ChatIdContext = createContext<ChatIdContextValue | null>(null);
12
12
  *
13
13
  * @returns The current chat ID, or null if not yet initialized
14
14
  */
15
- export const useChatId = () => {
15
+ export const useChatId = (): string | null => {
16
16
  const context = useContext(ChatIdContext);
17
17
  if (!context) {
18
18
  throw new Error("useChatId must be used within ElementsProvider");
@@ -36,7 +36,7 @@ interface ConnectionStatusProviderProps {
36
36
 
37
37
  export const ConnectionStatusProvider = ({
38
38
  children,
39
- }: ConnectionStatusProviderProps) => {
39
+ }: ConnectionStatusProviderProps): React.JSX.Element => {
40
40
  const [state, setState] = useState<ConnectionState>("connected");
41
41
  const [retryCount, setRetryCount] = useState(0);
42
42
  const [isOnline, setIsOnline] = useState(
@@ -138,7 +138,7 @@ export const ConnectionStatusProvider = ({
138
138
  );
139
139
  };
140
140
 
141
- export const useConnectionStatus = () => {
141
+ export const useConnectionStatus = (): ConnectionStatusContextValue => {
142
142
  const context = useContext(ConnectionStatusContext);
143
143
  if (!context) {
144
144
  throw new Error(
@@ -153,6 +153,7 @@ export const useConnectionStatus = () => {
153
153
  * Returns null if not within a ConnectionStatusProvider (for backwards compatibility).
154
154
  */
155
155
 
156
- export const useConnectionStatusOptional = () => {
157
- return useContext(ConnectionStatusContext);
158
- };
156
+ export const useConnectionStatusOptional =
157
+ (): ConnectionStatusContextValue | null => {
158
+ return useContext(ConnectionStatusContext);
159
+ };