@gram-ai/elements 1.27.4 → 1.27.6

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 (278) hide show
  1. package/README.md +72 -60
  2. package/README.typedoc.md +6 -6
  3. package/bin/cli.js +74 -74
  4. package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
  5. package/dist/{compat-shims-BPJ7Q68c.js → compat-shims-DxtUrORi.js} +4 -2
  6. package/dist/compat-shims-DxtUrORi.js.map +1 -0
  7. package/dist/components/ShareButton/index.d.ts +2 -2
  8. package/dist/components/assistant-ui/message-feedback.d.ts +1 -1
  9. package/dist/components/assistant-ui/tooltip-icon-button.d.ts +2 -2
  10. package/dist/components/ui/avatar.d.ts +2 -2
  11. package/dist/components/ui/button.d.ts +1 -1
  12. package/dist/components/ui/calendar.d.ts +1 -1
  13. package/dist/components/ui/collapsible.d.ts +1 -1
  14. package/dist/components/ui/dialog.d.ts +4 -4
  15. package/dist/components/ui/popover.d.ts +2 -2
  16. package/dist/components/ui/skeleton.d.ts +1 -1
  17. package/dist/components/ui/time-range-picker.d.ts +1 -1
  18. package/dist/components/ui/tool-ui.d.ts +7 -7
  19. package/dist/components/ui/tooltip.d.ts +2 -2
  20. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  21. package/dist/elements.cjs +1 -1
  22. package/dist/elements.css +1 -1
  23. package/dist/elements.js +2 -2
  24. package/dist/hooks/useDensity.d.ts +73 -73
  25. package/dist/hooks/useMCPTools.d.ts +1 -1
  26. package/dist/hooks/useRadius.d.ts +1 -1
  27. package/dist/{index-KSX4Qjip.cjs → index-A17b62wR.cjs} +10 -10
  28. package/dist/index-A17b62wR.cjs.map +1 -0
  29. package/dist/{index-BpJstUh1.cjs → index-C4bFBGfl.cjs} +4 -4
  30. package/dist/{index-BpJstUh1.cjs.map → index-C4bFBGfl.cjs.map} +1 -1
  31. package/dist/{index-CUitXazZ.js → index-D93pV0_o.js} +55 -55
  32. package/dist/{index-CUitXazZ.js.map → index-D93pV0_o.js.map} +1 -1
  33. package/dist/{index-D0bAYNQy.js → index-Dm2wLFTN.js} +304 -282
  34. package/dist/index-Dm2wLFTN.js.map +1 -0
  35. package/dist/lib/cassette.d.ts +4 -4
  36. package/dist/lib/errorTracking.d.ts +1 -1
  37. package/dist/lib/messageConverter.d.ts +1 -1
  38. package/dist/lib/models.d.ts +1 -1
  39. package/dist/plugins/chart/ui/bar-chart.d.ts +1 -1
  40. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  41. package/dist/plugins/generative-ui/ui/accordion.d.ts +1 -1
  42. package/dist/plugins/generative-ui/ui/action-button.d.ts +2 -2
  43. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +1 -1
  44. package/dist/plugins/generative-ui/ui/alert.d.ts +4 -4
  45. package/dist/plugins/generative-ui/ui/avatar.d.ts +5 -5
  46. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  47. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -2
  48. package/dist/plugins/generative-ui/ui/button.d.ts +2 -2
  49. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +2 -2
  50. package/dist/plugins/generative-ui/ui/card.d.ts +8 -8
  51. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  52. package/dist/plugins/generative-ui/ui/data-table.d.ts +2 -2
  53. package/dist/plugins/generative-ui/ui/dialog.d.ts +3 -3
  54. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +3 -3
  55. package/dist/plugins/generative-ui/ui/grid.d.ts +3 -3
  56. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +1 -1
  57. package/dist/plugins/generative-ui/ui/input.d.ts +2 -2
  58. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  59. package/dist/plugins/generative-ui/ui/metric.d.ts +3 -3
  60. package/dist/plugins/generative-ui/ui/pagination.d.ts +6 -6
  61. package/dist/plugins/generative-ui/ui/popover.d.ts +4 -4
  62. package/dist/plugins/generative-ui/ui/progress.d.ts +2 -2
  63. package/dist/plugins/generative-ui/ui/radio-group.d.ts +1 -1
  64. package/dist/plugins/generative-ui/ui/select.d.ts +2 -2
  65. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  66. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  67. package/dist/plugins/generative-ui/ui/stack.d.ts +6 -6
  68. package/dist/plugins/generative-ui/ui/switch.d.ts +2 -2
  69. package/dist/plugins/generative-ui/ui/table.d.ts +9 -9
  70. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +1 -1
  71. package/dist/plugins/generative-ui/ui/tabs.d.ts +1 -1
  72. package/dist/plugins/generative-ui/ui/text.d.ts +3 -3
  73. package/dist/plugins/generative-ui/ui/textarea.d.ts +2 -2
  74. package/dist/plugins/generative-ui/ui/tooltip.d.ts +1 -1
  75. package/dist/plugins.cjs +1 -1
  76. package/dist/plugins.js +1 -1
  77. package/dist/{profiler-CyzxBxVz.cjs → profiler-Cbbf4eEX.cjs} +2 -2
  78. package/dist/{profiler-CyzxBxVz.cjs.map → profiler-Cbbf4eEX.cjs.map} +1 -1
  79. package/dist/{profiler-BFkhZRxj.js → profiler-mca4IXaY.js} +2 -2
  80. package/dist/{profiler-BFkhZRxj.js.map → profiler-mca4IXaY.js.map} +1 -1
  81. package/dist/react-shim.js +1 -1
  82. package/dist/server/express.cjs.map +1 -1
  83. package/dist/server/express.js.map +1 -1
  84. package/dist/{startRecording-C-PPAs_Z.js → startRecording-BCafdS7B.js} +2 -2
  85. package/dist/{startRecording-C-PPAs_Z.js.map → startRecording-BCafdS7B.js.map} +1 -1
  86. package/dist/{startRecording-Dq92sEHf.cjs → startRecording-Eb5f7wqP.cjs} +2 -2
  87. package/dist/{startRecording-Dq92sEHf.cjs.map → startRecording-Eb5f7wqP.cjs.map} +1 -1
  88. package/dist/types/index.d.ts +4 -4
  89. package/package.json +1 -5
  90. package/src/compat-plugin.ts +14 -14
  91. package/src/compat-shims.ts +33 -31
  92. package/src/compat.test.ts +48 -48
  93. package/src/compat.ts +6 -6
  94. package/src/components/Chat/index.tsx +17 -17
  95. package/src/components/Chat/stories/Charts.stories.tsx +98 -98
  96. package/src/components/Chat/stories/Composer.stories.tsx +15 -15
  97. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +44 -44
  98. package/src/components/Chat/stories/CustomComponents.stories.tsx +17 -17
  99. package/src/components/Chat/stories/Density.stories.tsx +20 -20
  100. package/src/components/Chat/stories/ErrorBoundary.stories.tsx +47 -47
  101. package/src/components/Chat/stories/FrontendTools.stories.tsx +39 -39
  102. package/src/components/Chat/stories/GenerativeUI.stories.tsx +48 -48
  103. package/src/components/Chat/stories/MessageFeedback.stories.tsx +52 -52
  104. package/src/components/Chat/stories/Modal.stories.tsx +28 -28
  105. package/src/components/Chat/stories/Model.stories.tsx +11 -11
  106. package/src/components/Chat/stories/Radius.stories.tsx +20 -20
  107. package/src/components/Chat/stories/Sidecar.stories.tsx +13 -13
  108. package/src/components/Chat/stories/StyleIsolation.stories.tsx +11 -11
  109. package/src/components/Chat/stories/Theme.stories.tsx +25 -25
  110. package/src/components/Chat/stories/Thread.stories.tsx +25 -25
  111. package/src/components/Chat/stories/ToolApproval.stories.tsx +55 -55
  112. package/src/components/Chat/stories/ToolMentions.stories.tsx +17 -17
  113. package/src/components/Chat/stories/Tools.stories.tsx +88 -88
  114. package/src/components/Chat/stories/Variants.stories.tsx +32 -32
  115. package/src/components/Chat/stories/Welcome.stories.tsx +14 -14
  116. package/src/components/ChatHistory.tsx +7 -7
  117. package/src/components/FrontendTools/index.tsx +5 -5
  118. package/src/components/Replay.stories.tsx +157 -157
  119. package/src/components/Replay.tsx +76 -73
  120. package/src/components/ShadowRoot.tsx +40 -40
  121. package/src/components/ShareButton/index.tsx +32 -32
  122. package/src/components/assistant-ui/assistant-modal.tsx +92 -87
  123. package/src/components/assistant-ui/assistant-sidecar.tsx +35 -35
  124. package/src/components/assistant-ui/attachment.tsx +80 -80
  125. package/src/components/assistant-ui/connection-status-indicator.tsx +33 -33
  126. package/src/components/assistant-ui/error-boundary.tsx +34 -34
  127. package/src/components/assistant-ui/follow-on-suggestions.tsx +26 -26
  128. package/src/components/assistant-ui/markdown-text.tsx +69 -69
  129. package/src/components/assistant-ui/mentioned-tools-badges.tsx +38 -38
  130. package/src/components/assistant-ui/message-feedback.tsx +57 -50
  131. package/src/components/assistant-ui/reasoning.tsx +83 -83
  132. package/src/components/assistant-ui/thread-list.tsx +45 -45
  133. package/src/components/assistant-ui/thread.tsx +278 -278
  134. package/src/components/assistant-ui/tool-fallback.tsx +37 -37
  135. package/src/components/assistant-ui/tool-group.tsx +26 -26
  136. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +122 -122
  137. package/src/components/assistant-ui/tooltip-icon-button.tsx +18 -18
  138. package/src/components/ui/avatar.tsx +12 -12
  139. package/src/components/ui/button.tsx +12 -12
  140. package/src/components/ui/buttonVariants.ts +17 -17
  141. package/src/components/ui/calendar.tsx +106 -106
  142. package/src/components/ui/charts.stories.tsx +56 -56
  143. package/src/components/ui/collapsible.tsx +5 -5
  144. package/src/components/ui/dialog.tsx +30 -30
  145. package/src/components/ui/generative-ui.stories.tsx +200 -200
  146. package/src/components/ui/generative-ui.tsx +26 -26
  147. package/src/components/ui/popover.tsx +14 -14
  148. package/src/components/ui/skeleton.tsx +5 -5
  149. package/src/components/ui/time-range-picker.stories.tsx +80 -80
  150. package/src/components/ui/time-range-picker.tsx +248 -246
  151. package/src/components/ui/tool-ui.stories.tsx +37 -37
  152. package/src/components/ui/tool-ui.tsx +221 -215
  153. package/src/components/ui/tooltip.tsx +15 -15
  154. package/src/constants/tailwind.ts +1 -1
  155. package/src/contexts/ChatIdContext.tsx +7 -7
  156. package/src/contexts/ConnectionStatusContext.tsx +64 -64
  157. package/src/contexts/ElementsProvider.tsx +214 -213
  158. package/src/contexts/ReplayContext.ts +3 -3
  159. package/src/contexts/ToolApprovalContext.tsx +54 -54
  160. package/src/contexts/ToolExecutionContext.tsx +34 -34
  161. package/src/contexts/contexts.ts +7 -7
  162. package/src/contexts/portal-container-context.ts +2 -2
  163. package/src/contexts/portal-container.tsx +7 -7
  164. package/src/embedded.ts +1 -1
  165. package/src/global.css +25 -25
  166. package/src/hooks/useAuth.ts +72 -72
  167. package/src/hooks/useDensity.ts +79 -79
  168. package/src/hooks/useElements.ts +6 -6
  169. package/src/hooks/useExpanded.ts +12 -12
  170. package/src/hooks/useFollowOnSuggestions.ts +83 -83
  171. package/src/hooks/useGramThreadListAdapter.tsx +99 -99
  172. package/src/hooks/useMCPTools.ts +47 -47
  173. package/src/hooks/useModel.ts +14 -14
  174. package/src/hooks/usePluginComponents.ts +11 -11
  175. package/src/hooks/usePortalContainer.ts +5 -5
  176. package/src/hooks/useRadius.ts +23 -23
  177. package/src/hooks/useRecordCassette.ts +34 -34
  178. package/src/hooks/useSession.ts +11 -11
  179. package/src/hooks/useThemeProps.ts +13 -13
  180. package/src/hooks/useThreadId.ts +4 -4
  181. package/src/hooks/useToolApproval.ts +7 -7
  182. package/src/hooks/useToolMentions.ts +40 -40
  183. package/src/index.ts +26 -26
  184. package/src/lib/api.test.ts +61 -61
  185. package/src/lib/api.ts +4 -3
  186. package/src/lib/auth.ts +13 -13
  187. package/src/lib/cassette.ts +84 -84
  188. package/src/lib/easing.ts +1 -1
  189. package/src/lib/errorTracking.config.ts +5 -5
  190. package/src/lib/errorTracking.ts +29 -29
  191. package/src/lib/generative-ui.ts +7 -7
  192. package/src/lib/humanize.ts +3 -3
  193. package/src/lib/messageConverter.test.ts +130 -127
  194. package/src/lib/messageConverter.ts +196 -196
  195. package/src/lib/models.ts +28 -20
  196. package/src/lib/token.test.ts +56 -56
  197. package/src/lib/token.ts +14 -14
  198. package/src/lib/tool-mentions.ts +45 -45
  199. package/src/lib/tools.ts +66 -62
  200. package/src/lib/utils.ts +5 -5
  201. package/src/lib.d.ts +1 -1
  202. package/src/plugins/README.md +5 -5
  203. package/src/plugins/chart/catalog.ts +18 -18
  204. package/src/plugins/chart/chart.test.ts +31 -31
  205. package/src/plugins/chart/component.tsx +34 -34
  206. package/src/plugins/chart/index.ts +4 -4
  207. package/src/plugins/chart/ui/area-chart.tsx +42 -42
  208. package/src/plugins/chart/ui/bar-chart.tsx +46 -46
  209. package/src/plugins/chart/ui/donut-chart.tsx +48 -48
  210. package/src/plugins/chart/ui/index.ts +7 -7
  211. package/src/plugins/chart/ui/line-chart.tsx +43 -43
  212. package/src/plugins/chart/ui/pie-chart.tsx +44 -44
  213. package/src/plugins/chart/ui/radar-chart.tsx +33 -33
  214. package/src/plugins/chart/ui/scatter-chart.tsx +43 -43
  215. package/src/plugins/components/MacOSWindowFrame.tsx +15 -15
  216. package/src/plugins/components/PluginLoadingState.tsx +10 -10
  217. package/src/plugins/components/index.ts +1 -1
  218. package/src/plugins/generative-ui/catalog.ts +54 -54
  219. package/src/plugins/generative-ui/component.tsx +85 -85
  220. package/src/plugins/generative-ui/index.ts +4 -4
  221. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +16 -16
  222. package/src/plugins/generative-ui/ui/accordion.tsx +16 -16
  223. package/src/plugins/generative-ui/ui/action-button.tsx +28 -28
  224. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +8 -8
  225. package/src/plugins/generative-ui/ui/alert.tsx +20 -20
  226. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +7 -7
  227. package/src/plugins/generative-ui/ui/avatar.tsx +30 -30
  228. package/src/plugins/generative-ui/ui/badge.tsx +22 -22
  229. package/src/plugins/generative-ui/ui/button-wrapper.tsx +12 -12
  230. package/src/plugins/generative-ui/ui/button.tsx +28 -28
  231. package/src/plugins/generative-ui/ui/card-wrapper.tsx +8 -8
  232. package/src/plugins/generative-ui/ui/card.tsx +27 -27
  233. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +9 -9
  234. package/src/plugins/generative-ui/ui/checkbox.tsx +9 -9
  235. package/src/plugins/generative-ui/ui/data-table.tsx +8 -8
  236. package/src/plugins/generative-ui/ui/dialog.tsx +31 -31
  237. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +44 -44
  238. package/src/plugins/generative-ui/ui/grid.tsx +12 -12
  239. package/src/plugins/generative-ui/ui/index.ts +40 -40
  240. package/src/plugins/generative-ui/ui/input-wrapper.tsx +11 -11
  241. package/src/plugins/generative-ui/ui/input.tsx +9 -9
  242. package/src/plugins/generative-ui/ui/label.tsx +8 -8
  243. package/src/plugins/generative-ui/ui/list.tsx +11 -11
  244. package/src/plugins/generative-ui/ui/metric.tsx +23 -23
  245. package/src/plugins/generative-ui/ui/pagination.tsx +28 -28
  246. package/src/plugins/generative-ui/ui/popover.tsx +21 -21
  247. package/src/plugins/generative-ui/ui/progress.tsx +13 -13
  248. package/src/plugins/generative-ui/ui/radio-group.tsx +12 -12
  249. package/src/plugins/generative-ui/ui/select-wrapper.tsx +7 -7
  250. package/src/plugins/generative-ui/ui/select.tsx +37 -37
  251. package/src/plugins/generative-ui/ui/separator.tsx +9 -9
  252. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +10 -10
  253. package/src/plugins/generative-ui/ui/skeleton.tsx +5 -5
  254. package/src/plugins/generative-ui/ui/stack.tsx +28 -28
  255. package/src/plugins/generative-ui/ui/switch.tsx +11 -11
  256. package/src/plugins/generative-ui/ui/table.tsx +32 -32
  257. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +11 -11
  258. package/src/plugins/generative-ui/ui/tabs.tsx +26 -26
  259. package/src/plugins/generative-ui/ui/text.tsx +12 -12
  260. package/src/plugins/generative-ui/ui/textarea.tsx +7 -7
  261. package/src/plugins/generative-ui/ui/tooltip.tsx +12 -12
  262. package/src/plugins/index.ts +7 -7
  263. package/src/react-shim.ts +6 -6
  264. package/src/server/bun.ts +12 -12
  265. package/src/server/core.ts +25 -25
  266. package/src/server/express.ts +17 -15
  267. package/src/server/fastify.ts +14 -14
  268. package/src/server/hono.ts +9 -9
  269. package/src/server/nextjs.ts +12 -12
  270. package/src/server/tanstack-start.ts +12 -12
  271. package/src/server.ts +27 -27
  272. package/src/storybook.d.ts +4 -4
  273. package/src/types/index.ts +124 -124
  274. package/src/types/plugins.ts +7 -7
  275. package/src/vite-env.d.ts +12 -12
  276. package/dist/compat-shims-BPJ7Q68c.js.map +0 -1
  277. package/dist/index-D0bAYNQy.js.map +0 -1
  278. package/dist/index-KSX4Qjip.cjs.map +0 -1
@@ -1,55 +1,55 @@
1
1
  /* eslint-disable react-refresh/only-export-components */
2
- import * as React from 'react'
3
- import { CalendarIcon, ChevronDown, Zap } from 'lucide-react'
4
- import { generateObject } from 'ai'
5
- import { createOpenRouter } from '@openrouter/ai-sdk-provider'
6
- import { z } from 'zod'
2
+ import * as React from "react";
3
+ import { CalendarIcon, ChevronDown, Zap } from "lucide-react";
4
+ import { generateObject, LanguageModel } from "ai";
5
+ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
6
+ import { z } from "zod";
7
7
 
8
- import { cn } from '@/lib/utils'
9
- import { Popover, PopoverContent, PopoverTrigger } from './popover'
10
- import { Calendar } from './calendar'
8
+ import { cn } from "@/lib/utils";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover";
10
+ import { Calendar } from "./calendar";
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
13
  // Types
14
14
  // ---------------------------------------------------------------------------
15
15
 
16
16
  export interface TimeRange {
17
- from: Date
18
- to: Date
17
+ from: Date;
18
+ to: Date;
19
19
  }
20
20
 
21
21
  export type DateRangePreset =
22
- | '15m'
23
- | '1h'
24
- | '4h'
25
- | '1d'
26
- | '2d'
27
- | '3d'
28
- | '7d'
29
- | '15d'
30
- | '30d'
31
- | '90d'
22
+ | "15m"
23
+ | "1h"
24
+ | "4h"
25
+ | "1d"
26
+ | "2d"
27
+ | "3d"
28
+ | "7d"
29
+ | "15d"
30
+ | "30d"
31
+ | "90d";
32
32
 
33
33
  export interface TimeRangePreset {
34
- label: string
35
- shortLabel: string
36
- value: DateRangePreset
37
- getRange: () => TimeRange
34
+ label: string;
35
+ shortLabel: string;
36
+ value: DateRangePreset;
37
+ getRange: () => TimeRange;
38
38
  }
39
39
 
40
40
  // ---------------------------------------------------------------------------
41
41
  // Date Utilities (no external dependencies)
42
42
  // ---------------------------------------------------------------------------
43
43
 
44
- function formatDate(date: Date, pattern: 'short' | 'medium' = 'short'): string {
45
- if (pattern === 'short') {
46
- return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
44
+ function formatDate(date: Date, pattern: "short" | "medium" = "short"): string {
45
+ if (pattern === "short") {
46
+ return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
47
47
  }
48
- return date.toLocaleDateString('en-US', {
49
- month: 'short',
50
- day: 'numeric',
51
- year: 'numeric',
52
- })
48
+ return date.toLocaleDateString("en-US", {
49
+ month: "short",
50
+ day: "numeric",
51
+ year: "numeric",
52
+ });
53
53
  }
54
54
 
55
55
  // ---------------------------------------------------------------------------
@@ -58,107 +58,107 @@ function formatDate(date: Date, pattern: 'short' | 'medium' = 'short'): string {
58
58
 
59
59
  export const PRESETS: TimeRangePreset[] = [
60
60
  {
61
- label: 'Past 15 Minutes',
62
- shortLabel: '15m',
63
- value: '15m',
61
+ label: "Past 15 Minutes",
62
+ shortLabel: "15m",
63
+ value: "15m",
64
64
  getRange: () => ({
65
65
  from: new Date(Date.now() - 15 * 60 * 1000),
66
66
  to: new Date(),
67
67
  }),
68
68
  },
69
69
  {
70
- label: 'Past 1 Hour',
71
- shortLabel: '1h',
72
- value: '1h',
70
+ label: "Past 1 Hour",
71
+ shortLabel: "1h",
72
+ value: "1h",
73
73
  getRange: () => ({
74
74
  from: new Date(Date.now() - 60 * 60 * 1000),
75
75
  to: new Date(),
76
76
  }),
77
77
  },
78
78
  {
79
- label: 'Past 4 Hours',
80
- shortLabel: '4h',
81
- value: '4h',
79
+ label: "Past 4 Hours",
80
+ shortLabel: "4h",
81
+ value: "4h",
82
82
  getRange: () => ({
83
83
  from: new Date(Date.now() - 4 * 60 * 60 * 1000),
84
84
  to: new Date(),
85
85
  }),
86
86
  },
87
87
  {
88
- label: 'Past 1 Day',
89
- shortLabel: '1d',
90
- value: '1d',
88
+ label: "Past 1 Day",
89
+ shortLabel: "1d",
90
+ value: "1d",
91
91
  getRange: () => ({
92
92
  from: new Date(Date.now() - 24 * 60 * 60 * 1000),
93
93
  to: new Date(),
94
94
  }),
95
95
  },
96
96
  {
97
- label: 'Past 2 Days',
98
- shortLabel: '2d',
99
- value: '2d',
97
+ label: "Past 2 Days",
98
+ shortLabel: "2d",
99
+ value: "2d",
100
100
  getRange: () => ({
101
101
  from: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
102
102
  to: new Date(),
103
103
  }),
104
104
  },
105
105
  {
106
- label: 'Past 3 Days',
107
- shortLabel: '3d',
108
- value: '3d',
106
+ label: "Past 3 Days",
107
+ shortLabel: "3d",
108
+ value: "3d",
109
109
  getRange: () => ({
110
110
  from: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000),
111
111
  to: new Date(),
112
112
  }),
113
113
  },
114
114
  {
115
- label: 'Past 7 Days',
116
- shortLabel: '1w',
117
- value: '7d',
115
+ label: "Past 7 Days",
116
+ shortLabel: "1w",
117
+ value: "7d",
118
118
  getRange: () => ({
119
119
  from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
120
120
  to: new Date(),
121
121
  }),
122
122
  },
123
123
  {
124
- label: 'Past 15 Days',
125
- shortLabel: '15d',
126
- value: '15d',
124
+ label: "Past 15 Days",
125
+ shortLabel: "15d",
126
+ value: "15d",
127
127
  getRange: () => ({
128
128
  from: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
129
129
  to: new Date(),
130
130
  }),
131
131
  },
132
132
  {
133
- label: 'Past 1 Month',
134
- shortLabel: '1mo',
135
- value: '30d',
133
+ label: "Past 1 Month",
134
+ shortLabel: "1mo",
135
+ value: "30d",
136
136
  getRange: () => ({
137
137
  from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
138
138
  to: new Date(),
139
139
  }),
140
140
  },
141
141
  {
142
- label: 'Past 3 Months',
143
- shortLabel: '3mo',
144
- value: '90d',
142
+ label: "Past 3 Months",
143
+ shortLabel: "3mo",
144
+ value: "90d",
145
145
  getRange: () => ({
146
146
  from: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000),
147
147
  to: new Date(),
148
148
  }),
149
149
  },
150
- ]
150
+ ];
151
151
 
152
152
  // Badge width class - shared between trigger and dropdown for alignment
153
- const BADGE_WIDTH = 'min-w-10'
153
+ const BADGE_WIDTH = "min-w-10";
154
154
 
155
155
  export function getPresetRange(preset: DateRangePreset): TimeRange {
156
- const p = PRESETS.find((p) => p.value === preset)
157
- return p ? p.getRange() : PRESETS[5].getRange() // Default to 3d
156
+ const p = PRESETS.find((p) => p.value === preset);
157
+ return p ? p.getRange() : PRESETS[5].getRange(); // Default to 3d
158
158
  }
159
159
 
160
160
  function getPresetByValue(value: DateRangePreset): TimeRangePreset | undefined {
161
- return PRESETS.find((p) => p.value === value)
161
+ return PRESETS.find((p) => p.value === value);
162
162
  }
163
163
 
164
164
  // ---------------------------------------------------------------------------
@@ -166,17 +166,17 @@ function getPresetByValue(value: DateRangePreset): TimeRangePreset | undefined {
166
166
  // ---------------------------------------------------------------------------
167
167
 
168
168
  type ParseResult =
169
- | { type: 'preset'; preset: DateRangePreset }
170
- | { type: 'custom'; range: TimeRange; label?: string }
171
- | null
169
+ | { type: "preset"; preset: DateRangePreset }
170
+ | { type: "custom"; range: TimeRange; label?: string }
171
+ | null;
172
172
 
173
173
  const timeRangeSchema = z.object({
174
- from: z.string().describe('ISO8601 start date/time'),
175
- to: z.string().describe('ISO8601 end date/time'),
176
- label: z.string().describe('Short semantic label for the range'),
177
- })
174
+ from: z.string().describe("ISO8601 start date/time"),
175
+ to: z.string().describe("ISO8601 end date/time"),
176
+ label: z.string().describe("Short semantic label for the range"),
177
+ });
178
178
 
179
- const TIME_RANGE_MODEL = 'openai/gpt-4o-mini'
179
+ const TIME_RANGE_MODEL = "openai/gpt-5.4-mini";
180
180
 
181
181
  /**
182
182
  * Parse an ISO date string as a local date (ignoring timezone).
@@ -185,55 +185,55 @@ const TIME_RANGE_MODEL = 'openai/gpt-4o-mini'
185
185
  */
186
186
  function parseAsLocalDate(isoString: string): Date {
187
187
  // Try to extract just the date part and create a local date
188
- const dateMatch = isoString.match(/^(\d{4})-(\d{2})-(\d{2})/)
188
+ const dateMatch = isoString.match(/^(\d{4})-(\d{2})-(\d{2})/);
189
189
  if (dateMatch) {
190
- const [, year, month, day] = dateMatch
190
+ const [, year, month, day] = dateMatch;
191
191
  // Check if there's a time component
192
- const timeMatch = isoString.match(/T(\d{2}):(\d{2}):?(\d{2})?/)
192
+ const timeMatch = isoString.match(/T(\d{2}):(\d{2}):?(\d{2})?/);
193
193
  if (timeMatch) {
194
- const [, hours, minutes, seconds = '0'] = timeMatch
194
+ const [, hours, minutes, seconds = "0"] = timeMatch;
195
195
  return new Date(
196
196
  parseInt(year),
197
197
  parseInt(month) - 1,
198
198
  parseInt(day),
199
199
  parseInt(hours),
200
200
  parseInt(minutes),
201
- parseInt(seconds)
202
- )
201
+ parseInt(seconds),
202
+ );
203
203
  }
204
204
  // Date only - use start of day local time
205
- return new Date(parseInt(year), parseInt(month) - 1, parseInt(day))
205
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
206
206
  }
207
207
  // Fallback to standard parsing
208
- return new Date(isoString)
208
+ return new Date(isoString);
209
209
  }
210
210
 
211
211
  async function parseWithAI(
212
212
  input: string,
213
213
  apiUrl: string,
214
- projectSlug?: string
214
+ projectSlug?: string,
215
215
  ): Promise<ParseResult> {
216
216
  try {
217
- const now = new Date()
217
+ const now = new Date();
218
218
 
219
219
  // Create OpenRouter provider without X-Gram-Source header (so usage is billed)
220
- const headers: Record<string, string> = {}
220
+ const headers: Record<string, string> = {};
221
221
  if (projectSlug) {
222
- headers['Gram-Project'] = projectSlug
222
+ headers["Gram-Project"] = projectSlug;
223
223
  }
224
224
 
225
225
  const openRouter = createOpenRouter({
226
226
  baseURL: apiUrl,
227
- apiKey: 'unused',
227
+ apiKey: "unused",
228
228
  headers,
229
229
  fetch: (url, init) =>
230
230
  fetch(url, {
231
231
  ...init,
232
- credentials: 'include',
232
+ credentials: "include",
233
233
  }),
234
- })
234
+ });
235
235
 
236
- const model = openRouter.chat(TIME_RANGE_MODEL)
236
+ const model = openRouter.chat(TIME_RANGE_MODEL) as LanguageModel;
237
237
 
238
238
  const result = await generateObject({
239
239
  model,
@@ -265,33 +265,33 @@ Examples:
265
265
  - "jan 5 to jan 10" -> label: "1/5-1/10"
266
266
 
267
267
  User input: ${input}`,
268
- })
268
+ });
269
269
 
270
- const parsed = result.object
270
+ const parsed = result.object;
271
271
  // Parse dates as local to avoid timezone shifts
272
- const from = parseAsLocalDate(parsed.from)
273
- const to = parseAsLocalDate(parsed.to)
272
+ const from = parseAsLocalDate(parsed.from);
273
+ const to = parseAsLocalDate(parsed.to);
274
274
 
275
275
  if (isNaN(from.getTime()) || isNaN(to.getTime())) {
276
- return null
276
+ return null;
277
277
  }
278
278
 
279
279
  // Normalize labels like "1w" -> "7d", "2w" -> "14d"
280
- let normalizedLabel = parsed.label
281
- if (normalizedLabel === '1w') normalizedLabel = '7d'
282
- if (normalizedLabel === '2w') normalizedLabel = '14d'
283
- if (normalizedLabel === '1mo') normalizedLabel = '30d'
284
- if (normalizedLabel === '3mo') normalizedLabel = '90d'
280
+ let normalizedLabel = parsed.label;
281
+ if (normalizedLabel === "1w") normalizedLabel = "7d";
282
+ if (normalizedLabel === "2w") normalizedLabel = "14d";
283
+ if (normalizedLabel === "1mo") normalizedLabel = "30d";
284
+ if (normalizedLabel === "3mo") normalizedLabel = "90d";
285
285
 
286
- const matchedPreset = PRESETS.find((p) => p.value === normalizedLabel)
286
+ const matchedPreset = PRESETS.find((p) => p.value === normalizedLabel);
287
287
  if (matchedPreset) {
288
- return { type: 'preset', preset: matchedPreset.value }
288
+ return { type: "preset", preset: matchedPreset.value };
289
289
  }
290
290
 
291
291
  // Use the semantic label from AI (e.g., "Mon", "Jan", "2024", "1/5-1/10")
292
- return { type: 'custom', range: { from, to }, label: parsed.label }
292
+ return { type: "custom", range: { from, to }, label: parsed.label };
293
293
  } catch {
294
- return null
294
+ return null;
295
295
  }
296
296
  }
297
297
 
@@ -301,33 +301,33 @@ User input: ${input}`,
301
301
 
302
302
  export interface TimeRangePickerProps {
303
303
  /** Current preset value */
304
- preset?: DateRangePreset | null
304
+ preset?: DateRangePreset | null;
305
305
  /** Current custom range */
306
- customRange?: TimeRange | null
306
+ customRange?: TimeRange | null;
307
307
  /** Called when a preset is selected */
308
- onPresetChange?: (preset: DateRangePreset) => void
308
+ onPresetChange?: (preset: DateRangePreset) => void;
309
309
  /** Called when a custom range is selected */
310
- onCustomRangeChange?: (from: Date, to: Date, label?: string) => void
310
+ onCustomRangeChange?: (from: Date, to: Date, label?: string) => void;
311
311
  /** Called to clear custom range */
312
- onClearCustomRange?: () => void
312
+ onClearCustomRange?: () => void;
313
313
  /** Initial label for custom range (from URL params) */
314
- customRangeLabel?: string | null
314
+ customRangeLabel?: string | null;
315
315
  /** Show LIVE mode option */
316
- showLive?: boolean
316
+ showLive?: boolean;
317
317
  /** Is LIVE mode active */
318
- isLive?: boolean
318
+ isLive?: boolean;
319
319
  /** Called when LIVE mode changes */
320
- onLiveChange?: (isLive: boolean) => void
320
+ onLiveChange?: (isLive: boolean) => void;
321
321
  /** Disabled state */
322
- disabled?: boolean
322
+ disabled?: boolean;
323
323
  /** Timezone display (e.g., "UTC-08:00") */
324
- timezone?: string
324
+ timezone?: string;
325
325
  /** API URL for AI parsing (defaults to window.location.origin) */
326
- apiUrl?: string
326
+ apiUrl?: string;
327
327
  /** Project slug for API authentication */
328
- projectSlug?: string
328
+ projectSlug?: string;
329
329
  /** Additional class name for the trigger */
330
- className?: string
330
+ className?: string;
331
331
  }
332
332
 
333
333
  function TimeRangePicker({
@@ -346,174 +346,174 @@ function TimeRangePicker({
346
346
  projectSlug,
347
347
  className,
348
348
  }: TimeRangePickerProps) {
349
- const [isOpen, setIsOpen] = React.useState(false)
350
- const [showCalendar, setShowCalendar] = React.useState(false)
351
- const [inputValue, setInputValue] = React.useState('')
352
- const [isEditing, setIsEditing] = React.useState(false)
353
- const [isParsing, setIsParsing] = React.useState(false)
349
+ const [isOpen, setIsOpen] = React.useState(false);
350
+ const [showCalendar, setShowCalendar] = React.useState(false);
351
+ const [inputValue, setInputValue] = React.useState("");
352
+ const [isEditing, setIsEditing] = React.useState(false);
353
+ const [isParsing, setIsParsing] = React.useState(false);
354
354
  const [customLabel, setCustomLabel] = React.useState<string | null>(
355
- initialCustomLabel || null
356
- )
357
- const inputRef = React.useRef<HTMLInputElement>(null)
355
+ initialCustomLabel || null,
356
+ );
357
+ const inputRef = React.useRef<HTMLInputElement>(null);
358
358
 
359
359
  // Sync custom label from props (e.g., when URL changes)
360
360
  React.useEffect(() => {
361
361
  if (initialCustomLabel !== undefined) {
362
- setCustomLabel(initialCustomLabel || null)
362
+ setCustomLabel(initialCustomLabel || null);
363
363
  }
364
- }, [initialCustomLabel])
364
+ }, [initialCustomLabel]);
365
365
 
366
366
  const effectiveApiUrl =
367
- apiUrl || (typeof window !== 'undefined' ? window.location.origin : '')
367
+ apiUrl || (typeof window !== "undefined" ? window.location.origin : "");
368
368
 
369
369
  const handlePresetClick = (p: TimeRangePreset) => {
370
- onPresetChange?.(p.value)
371
- setCustomLabel(null)
372
- setIsOpen(false)
373
- setInputValue('')
374
- }
370
+ onPresetChange?.(p.value);
371
+ setCustomLabel(null);
372
+ setIsOpen(false);
373
+ setInputValue("");
374
+ };
375
375
 
376
376
  const handleLiveClick = () => {
377
- onLiveChange?.(!isLive)
377
+ onLiveChange?.(!isLive);
378
378
  if (!isLive) {
379
379
  // When enabling LIVE, also select a default short preset
380
- onPresetChange?.('15m')
380
+ onPresetChange?.("15m");
381
381
  }
382
- setIsOpen(false)
383
- }
382
+ setIsOpen(false);
383
+ };
384
384
 
385
385
  const handleCalendarSelect = (range: { start: Date; end: Date | null }) => {
386
386
  if (range.start && range.end) {
387
- onCustomRangeChange?.(range.start, range.end)
388
- setCustomLabel(null) // Calendar selections don't have AI labels
389
- setIsOpen(false)
390
- setShowCalendar(false)
391
- setInputValue('')
387
+ onCustomRangeChange?.(range.start, range.end);
388
+ setCustomLabel(null); // Calendar selections don't have AI labels
389
+ setIsOpen(false);
390
+ setShowCalendar(false);
391
+ setInputValue("");
392
392
  }
393
- }
393
+ };
394
394
 
395
395
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
396
- setInputValue(e.target.value)
397
- }
396
+ setInputValue(e.target.value);
397
+ };
398
398
 
399
399
  const applyParseResult = (parsed: ParseResult) => {
400
400
  if (parsed) {
401
- if (parsed.type === 'preset') {
402
- onPresetChange?.(parsed.preset)
403
- setCustomLabel(null)
401
+ if (parsed.type === "preset") {
402
+ onPresetChange?.(parsed.preset);
403
+ setCustomLabel(null);
404
404
  } else {
405
- const label = parsed.label || undefined
406
- onCustomRangeChange?.(parsed.range.from, parsed.range.to, label)
407
- setCustomLabel(label || null)
405
+ const label = parsed.label || undefined;
406
+ onCustomRangeChange?.(parsed.range.from, parsed.range.to, label);
407
+ setCustomLabel(label || null);
408
408
  }
409
- setInputValue('')
410
- setIsOpen(false)
411
- setIsEditing(false)
412
- return true
409
+ setInputValue("");
410
+ setIsOpen(false);
411
+ setIsEditing(false);
412
+ return true;
413
413
  }
414
- return false
415
- }
414
+ return false;
415
+ };
416
416
 
417
417
  const handleInputKeyDown = async (
418
- e: React.KeyboardEvent<HTMLInputElement>
418
+ e: React.KeyboardEvent<HTMLInputElement>,
419
419
  ) => {
420
- if (e.key === 'Enter' && inputValue.trim() && !isParsing) {
420
+ if (e.key === "Enter" && inputValue.trim() && !isParsing) {
421
421
  // Use AI to parse natural language input
422
- setIsParsing(true)
422
+ setIsParsing(true);
423
423
  try {
424
424
  const aiParsed = await parseWithAI(
425
425
  inputValue,
426
426
  effectiveApiUrl,
427
- projectSlug
428
- )
429
- applyParseResult(aiParsed)
427
+ projectSlug,
428
+ );
429
+ applyParseResult(aiParsed);
430
430
  } finally {
431
- setIsParsing(false)
431
+ setIsParsing(false);
432
432
  }
433
- } else if (e.key === 'Escape') {
434
- setInputValue('')
435
- setIsEditing(false)
436
- setIsOpen(false)
437
- } else if (e.key === 'Backspace' && inputValue === '' && customRange) {
433
+ } else if (e.key === "Escape") {
434
+ setInputValue("");
435
+ setIsEditing(false);
436
+ setIsOpen(false);
437
+ } else if (e.key === "Backspace" && inputValue === "" && customRange) {
438
438
  // Clear custom range when backspacing on empty input
439
- e.preventDefault()
440
- onClearCustomRange?.()
439
+ e.preventDefault();
440
+ onClearCustomRange?.();
441
441
  }
442
- }
442
+ };
443
443
 
444
444
  const handleInputClick = (e: React.MouseEvent) => {
445
445
  // Prevent the popover trigger from toggling closed
446
- e.stopPropagation()
447
- setIsEditing(true)
448
- setIsOpen(true)
449
- }
446
+ e.stopPropagation();
447
+ setIsEditing(true);
448
+ setIsOpen(true);
449
+ };
450
450
 
451
451
  const handleInputFocus = () => {
452
- setIsEditing(true)
452
+ setIsEditing(true);
453
453
  // Don't set isOpen here - let the click handler or popover manage it
454
- }
454
+ };
455
455
 
456
456
  const handleInputBlur = () => {
457
457
  // Delay to allow click events on dropdown items
458
458
  setTimeout(() => {
459
459
  if (!inputValue) {
460
- setIsEditing(false)
460
+ setIsEditing(false);
461
461
  }
462
- }, 150)
463
- }
462
+ }, 150);
463
+ };
464
464
 
465
465
  // Determine current range for display
466
- const currentRange = customRange ?? (preset ? getPresetRange(preset) : null)
466
+ const currentRange = customRange ?? (preset ? getPresetRange(preset) : null);
467
467
 
468
468
  // Get short label for trigger badge
469
469
  const getShortLabel = () => {
470
- if (customRange) return customLabel || 'Custom'
470
+ if (customRange) return customLabel || "Custom";
471
471
  if (preset) {
472
- const presetObj = getPresetByValue(preset)
473
- return presetObj?.shortLabel ?? preset
472
+ const presetObj = getPresetByValue(preset);
473
+ return presetObj?.shortLabel ?? preset;
474
474
  }
475
- return '7d'
476
- }
475
+ return "7d";
476
+ };
477
477
 
478
478
  // Get label text (preset label or custom range description)
479
479
  const getLabelText = () => {
480
480
  if (customRange) {
481
- return `${formatDate(customRange.from)} – ${formatDate(customRange.to)}`
481
+ return `${formatDate(customRange.from)} – ${formatDate(customRange.to)}`;
482
482
  }
483
483
  if (preset) {
484
- const presetObj = getPresetByValue(preset)
485
- return presetObj?.label ?? 'Select time range'
484
+ const presetObj = getPresetByValue(preset);
485
+ return presetObj?.label ?? "Select time range";
486
486
  }
487
- return 'Select time range'
488
- }
487
+ return "Select time range";
488
+ };
489
489
 
490
490
  const handleOpenChange = (open: boolean) => {
491
491
  // If closing while editing, keep it open unless explicitly closed via selection
492
492
  if (!open && isEditing) {
493
- return
493
+ return;
494
494
  }
495
- setIsOpen(open)
495
+ setIsOpen(open);
496
496
  if (open && inputRef.current) {
497
497
  // Focus input when opening
498
- setTimeout(() => inputRef.current?.focus(), 0)
498
+ setTimeout(() => inputRef.current?.focus(), 0);
499
499
  }
500
- }
500
+ };
501
501
 
502
502
  return (
503
503
  <Popover open={isOpen} onOpenChange={handleOpenChange}>
504
504
  <PopoverTrigger asChild disabled={disabled}>
505
505
  <div
506
506
  className={cn(
507
- 'relative inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm transition-all outline-none',
508
- 'border-border hover:border-border/80',
509
- disabled && 'cursor-not-allowed opacity-50',
510
- timezone && 'pt-4',
511
- className
507
+ "relative inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm transition-all outline-none",
508
+ "border-border hover:border-border/80",
509
+ disabled && "cursor-not-allowed opacity-50",
510
+ timezone && "pt-4",
511
+ className,
512
512
  )}
513
513
  >
514
514
  {/* Floating timezone legend */}
515
515
  {timezone && (
516
- <span className="bg-background text-muted-foreground absolute -top-2 left-3 px-1 text-xs">
516
+ <span className="absolute -top-2 left-3 bg-background px-1 text-xs text-muted-foreground">
517
517
  {timezone}
518
518
  </span>
519
519
  )}
@@ -521,11 +521,11 @@ function TimeRangePicker({
521
521
  {/* Short badge */}
522
522
  <span
523
523
  className={cn(
524
- 'inline-flex h-6 items-center justify-center rounded px-2 py-1 text-xs font-semibold',
524
+ "inline-flex h-6 items-center justify-center rounded px-2 py-1 text-xs font-semibold",
525
525
  BADGE_WIDTH,
526
526
  isLive
527
- ? 'bg-green-500 text-white'
528
- : 'bg-muted text-muted-foreground'
527
+ ? "bg-green-500 text-white"
528
+ : "bg-muted text-muted-foreground",
529
529
  )}
530
530
  >
531
531
  {isParsing ? (
@@ -548,39 +548,40 @@ function TimeRangePicker({
548
548
  placeholder="e.g., 3 days ago, last week..."
549
549
  disabled={disabled}
550
550
  className={cn(
551
- 'min-w-[140px] flex-1 bg-transparent outline-none',
552
- 'placeholder:text-muted-foreground/60',
553
- !isEditing && 'cursor-pointer',
554
- disabled && 'cursor-not-allowed'
551
+ "min-w-[140px] flex-1 bg-transparent outline-none",
552
+ "placeholder:text-muted-foreground/60",
553
+ !isEditing && "cursor-pointer",
554
+ disabled && "cursor-not-allowed",
555
555
  )}
556
556
  />
557
557
 
558
558
  {/* Dropdown chevron */}
559
- <ChevronDown className="text-muted-foreground h-4 w-4 shrink-0" />
559
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
560
560
  </div>
561
561
  </PopoverTrigger>
562
562
 
563
563
  <PopoverContent
564
- className="w-64 p-0"
565
- align="start"
564
+ className="w-fit max-w-[500px] p-0"
565
+ align="end"
566
+ collisionPadding={16}
566
567
  onOpenAutoFocus={(e) => {
567
568
  // Prevent popover from stealing focus from the input
568
- e.preventDefault()
569
- inputRef.current?.focus()
569
+ e.preventDefault();
570
+ inputRef.current?.focus();
570
571
  }}
571
572
  >
572
573
  <div className="flex flex-col">
573
574
  {/* Calendar view */}
574
575
  {showCalendar ? (
575
576
  <>
576
- <div className="border-border/50 flex items-center justify-between border-b px-3 py-2">
577
- <span className="text-muted-foreground text-xs font-medium">
577
+ <div className="flex items-center justify-between border-b border-border/50 px-3 py-2">
578
+ <span className="text-xs font-medium text-muted-foreground">
578
579
  Select date range
579
580
  </span>
580
581
  <button
581
582
  type="button"
582
583
  onClick={() => setShowCalendar(false)}
583
- className="text-primary text-xs hover:underline"
584
+ className="text-xs text-primary hover:underline"
584
585
  >
585
586
  Back
586
587
  </button>
@@ -594,14 +595,14 @@ function TimeRangePicker({
594
595
  maxDate={new Date()}
595
596
  />
596
597
  {customRange && onClearCustomRange && (
597
- <div className="border-border/50 border-t p-2">
598
+ <div className="border-t border-border/50 p-2">
598
599
  <button
599
600
  type="button"
600
601
  onClick={() => {
601
- onClearCustomRange()
602
- setShowCalendar(false)
602
+ onClearCustomRange();
603
+ setShowCalendar(false);
603
604
  }}
604
- className="text-muted-foreground hover:text-foreground w-full text-xs transition-colors"
605
+ className="w-full text-xs text-muted-foreground transition-colors hover:text-foreground"
605
606
  >
606
607
  Clear custom range
607
608
  </button>
@@ -617,18 +618,18 @@ function TimeRangePicker({
617
618
  type="button"
618
619
  onClick={handleLiveClick}
619
620
  className={cn(
620
- 'flex w-full items-center gap-3 px-3 py-2 text-sm transition-colors',
621
- 'hover:bg-muted',
622
- isLive && 'bg-blue-500/10'
621
+ "flex w-full items-center gap-3 px-3 py-2 text-sm transition-colors",
622
+ "hover:bg-muted",
623
+ isLive && "bg-blue-500/10",
623
624
  )}
624
625
  >
625
626
  <span
626
627
  className={cn(
627
- 'inline-flex items-center justify-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold',
628
+ "inline-flex items-center justify-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold",
628
629
  BADGE_WIDTH,
629
630
  isLive
630
- ? 'bg-green-500 text-white'
631
- : 'bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400'
631
+ ? "bg-green-500 text-white"
632
+ : "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400",
632
633
  )}
633
634
  >
634
635
  <Zap className="h-3 w-3" />
@@ -640,37 +641,38 @@ function TimeRangePicker({
640
641
 
641
642
  {/* Preset options */}
642
643
  {PRESETS.map((p) => {
643
- const isSelected = preset === p.value && !customRange && !isLive
644
+ const isSelected =
645
+ preset === p.value && !customRange && !isLive;
644
646
  return (
645
647
  <button
646
648
  key={p.value}
647
649
  type="button"
648
650
  onClick={() => handlePresetClick(p)}
649
651
  className={cn(
650
- 'flex w-full items-center gap-3 px-3 py-2 text-sm transition-colors',
651
- isSelected ? 'bg-blue-500 text-white' : 'hover:bg-muted'
652
+ "flex w-full items-center gap-3 px-3 py-2 text-sm transition-colors",
653
+ isSelected ? "bg-blue-500 text-white" : "hover:bg-muted",
652
654
  )}
653
655
  >
654
656
  <span
655
657
  className={cn(
656
- 'inline-flex items-center justify-center rounded px-1.5 py-0.5 text-xs font-semibold',
658
+ "inline-flex items-center justify-center rounded px-1.5 py-0.5 text-xs font-semibold",
657
659
  BADGE_WIDTH,
658
660
  isSelected
659
- ? 'bg-white/20 text-white'
660
- : 'bg-muted text-muted-foreground'
661
+ ? "bg-white/20 text-white"
662
+ : "bg-muted text-muted-foreground",
661
663
  )}
662
664
  >
663
665
  {p.shortLabel}
664
666
  </span>
665
667
  <span
666
668
  className={
667
- isSelected ? 'text-white' : 'text-foreground/80'
669
+ isSelected ? "text-white" : "text-foreground/80"
668
670
  }
669
671
  >
670
672
  {p.label}
671
673
  </span>
672
674
  </button>
673
- )
675
+ );
674
676
  })}
675
677
 
676
678
  {/* Select from calendar */}
@@ -678,23 +680,23 @@ function TimeRangePicker({
678
680
  type="button"
679
681
  onClick={() => setShowCalendar(true)}
680
682
  className={cn(
681
- 'flex w-full items-center gap-3 px-3 py-2 text-sm transition-colors',
682
- customRange ? 'bg-blue-500 text-white' : 'hover:bg-muted'
683
+ "flex w-full items-center gap-3 px-3 py-2 text-sm transition-colors",
684
+ customRange ? "bg-blue-500 text-white" : "hover:bg-muted",
683
685
  )}
684
686
  >
685
687
  <span
686
688
  className={cn(
687
- 'inline-flex items-center justify-center rounded px-1.5 py-0.5',
689
+ "inline-flex items-center justify-center rounded px-1.5 py-0.5",
688
690
  BADGE_WIDTH,
689
691
  customRange
690
- ? 'bg-white/20 text-white'
691
- : 'bg-muted text-muted-foreground'
692
+ ? "bg-white/20 text-white"
693
+ : "bg-muted text-muted-foreground",
692
694
  )}
693
695
  >
694
696
  <CalendarIcon className="h-4 w-4" />
695
697
  </span>
696
698
  <span
697
- className={customRange ? 'text-white' : 'text-foreground/80'}
699
+ className={customRange ? "text-white" : "text-foreground/80"}
698
700
  >
699
701
  Select from calendar...
700
702
  </span>
@@ -704,8 +706,8 @@ function TimeRangePicker({
704
706
  </div>
705
707
  </PopoverContent>
706
708
  </Popover>
707
- )
709
+ );
708
710
  }
709
- TimeRangePicker.displayName = 'TimeRangePicker'
711
+ TimeRangePicker.displayName = "TimeRangePicker";
710
712
 
711
- export { TimeRangePicker }
713
+ export { TimeRangePicker };