@gram-ai/elements 1.26.1 → 1.27.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.
- package/README.md +83 -15
- package/dist/components/Chat/stories/ConnectionConfiguration.stories.d.ts +1 -1
- package/dist/components/ui/calendar.d.ts +25 -0
- package/dist/components/ui/time-range-picker.d.ts +46 -0
- package/dist/components/ui/time-range-picker.stories.d.ts +37 -0
- package/dist/core-Cqad6-xW.js +36 -0
- package/dist/core-Cqad6-xW.js.map +1 -0
- package/dist/core-DBxmxwCi.cjs +2 -0
- package/dist/core-DBxmxwCi.cjs.map +1 -0
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +18 -14
- package/dist/hooks/useModel.d.ts +2 -0
- package/dist/index-CP-wWZCV.cjs +172 -0
- package/dist/index-CP-wWZCV.cjs.map +1 -0
- package/dist/{index-DfqYP0CD.js → index-oO5BAmPI.js} +12578 -11964
- package/dist/index-oO5BAmPI.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/lib/auth.d.ts +12 -4
- package/dist/lib/models.d.ts +1 -1
- package/dist/{profiler-ZLr2-8s7.cjs → profiler-CEpc7O5Q.cjs} +2 -2
- package/dist/{profiler-ZLr2-8s7.cjs.map → profiler-CEpc7O5Q.cjs.map} +1 -1
- package/dist/{profiler-WoFj2UH8.js → profiler-ECh1zoXF.js} +2 -2
- package/dist/{profiler-WoFj2UH8.js.map → profiler-ECh1zoXF.js.map} +1 -1
- package/dist/server/bun.cjs +2 -0
- package/dist/server/bun.cjs.map +1 -0
- package/dist/server/bun.d.ts +8 -0
- package/dist/server/bun.js +26 -0
- package/dist/server/bun.js.map +1 -0
- package/dist/server/core.d.ts +37 -0
- package/dist/server/express.cjs +2 -0
- package/dist/server/express.cjs.map +1 -0
- package/dist/server/express.d.ts +9 -0
- package/dist/server/express.js +21 -0
- package/dist/server/express.js.map +1 -0
- package/dist/server/fastify.cjs +2 -0
- package/dist/server/fastify.cjs.map +1 -0
- package/dist/server/fastify.d.ts +9 -0
- package/dist/server/fastify.js +19 -0
- package/dist/server/fastify.js.map +1 -0
- package/dist/server/hono.cjs +2 -0
- package/dist/server/hono.cjs.map +1 -0
- package/dist/server/hono.d.ts +9 -0
- package/dist/server/hono.js +20 -0
- package/dist/server/hono.js.map +1 -0
- package/dist/server/nextjs.cjs +2 -0
- package/dist/server/nextjs.cjs.map +1 -0
- package/dist/server/nextjs.d.ts +8 -0
- package/dist/server/nextjs.js +26 -0
- package/dist/server/nextjs.js.map +1 -0
- package/dist/server/tanstack-start.cjs +2 -0
- package/dist/server/tanstack-start.cjs.map +1 -0
- package/dist/server/tanstack-start.d.ts +25 -0
- package/dist/server/tanstack-start.js +39 -0
- package/dist/server/tanstack-start.js.map +1 -0
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts +10 -16
- package/dist/server.js +22 -29
- package/dist/server.js.map +1 -1
- package/dist/{startRecording-DzQo16WK.js → startRecording-CmZjjJoz.js} +2 -2
- package/dist/{startRecording-DzQo16WK.js.map → startRecording-CmZjjJoz.js.map} +1 -1
- package/dist/{startRecording-BGnWDInp.cjs → startRecording-qDCAu4Q0.cjs} +2 -2
- package/dist/{startRecording-BGnWDInp.cjs.map → startRecording-qDCAu4Q0.cjs.map} +1 -1
- package/dist/types/index.d.ts +22 -10
- package/package.json +63 -3
- package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -8
- package/src/components/assistant-ui/thread.tsx +8 -14
- package/src/components/ui/calendar.tsx +262 -0
- package/src/components/ui/time-range-picker.stories.tsx +249 -0
- package/src/components/ui/time-range-picker.tsx +675 -0
- package/src/hooks/useAuth.ts +59 -7
- package/src/hooks/useFollowOnSuggestions.ts +7 -14
- package/src/hooks/useModel.ts +30 -0
- package/src/index.ts +17 -0
- package/src/lib/api.test.ts +4 -4
- package/src/lib/auth.ts +34 -4
- package/src/lib/models.ts +1 -0
- package/src/server/bun.ts +63 -0
- package/src/server/core.ts +84 -0
- package/src/server/express.ts +60 -0
- package/src/server/fastify.ts +61 -0
- package/src/server/hono.ts +55 -0
- package/src/server/nextjs.ts +58 -0
- package/src/server/tanstack-start.ts +110 -0
- package/src/server.ts +37 -49
- package/src/types/index.ts +25 -9
- package/dist/index-DdrZQXwQ.cjs +0 -147
- package/dist/index-DdrZQXwQ.cjs.map +0 -1
- package/dist/index-DfqYP0CD.js.map +0 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
export interface CalendarProps {
|
|
7
|
+
/** Selected date range */
|
|
8
|
+
selected?: { start: Date | null; end: Date | null }
|
|
9
|
+
/** Called when a date or range is selected */
|
|
10
|
+
onSelect?: (range: { start: Date; end: Date | null }) => void
|
|
11
|
+
/** Whether range selection is enabled */
|
|
12
|
+
mode?: 'single' | 'range'
|
|
13
|
+
/** Disable dates before this */
|
|
14
|
+
minDate?: Date
|
|
15
|
+
/** Disable dates after this */
|
|
16
|
+
maxDate?: Date
|
|
17
|
+
/** Additional className */
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface CalendarDay {
|
|
22
|
+
date: Date
|
|
23
|
+
isCurrentMonth: boolean
|
|
24
|
+
isToday: boolean
|
|
25
|
+
isSelected: boolean
|
|
26
|
+
isInRange: boolean
|
|
27
|
+
isRangeStart: boolean
|
|
28
|
+
isRangeEnd: boolean
|
|
29
|
+
isDisabled: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
|
33
|
+
const MONTHS = [
|
|
34
|
+
'January',
|
|
35
|
+
'February',
|
|
36
|
+
'March',
|
|
37
|
+
'April',
|
|
38
|
+
'May',
|
|
39
|
+
'June',
|
|
40
|
+
'July',
|
|
41
|
+
'August',
|
|
42
|
+
'September',
|
|
43
|
+
'October',
|
|
44
|
+
'November',
|
|
45
|
+
'December',
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
function isSameDay(a: Date, b: Date): boolean {
|
|
49
|
+
return (
|
|
50
|
+
a.getFullYear() === b.getFullYear() &&
|
|
51
|
+
a.getMonth() === b.getMonth() &&
|
|
52
|
+
a.getDate() === b.getDate()
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isInRange(date: Date, start: Date | null, end: Date | null): boolean {
|
|
57
|
+
if (!start || !end) return false
|
|
58
|
+
const time = date.getTime()
|
|
59
|
+
return time >= start.getTime() && time <= end.getTime()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getCalendarDays(
|
|
63
|
+
year: number,
|
|
64
|
+
month: number,
|
|
65
|
+
selected: { start: Date | null; end: Date | null },
|
|
66
|
+
minDate?: Date,
|
|
67
|
+
maxDate?: Date
|
|
68
|
+
): CalendarDay[] {
|
|
69
|
+
const today = new Date()
|
|
70
|
+
const firstDay = new Date(year, month, 1)
|
|
71
|
+
const lastDay = new Date(year, month + 1, 0)
|
|
72
|
+
const startPadding = firstDay.getDay()
|
|
73
|
+
const days: CalendarDay[] = []
|
|
74
|
+
|
|
75
|
+
// Add days from previous month
|
|
76
|
+
const prevMonthLastDay = new Date(year, month, 0)
|
|
77
|
+
for (let i = startPadding - 1; i >= 0; i--) {
|
|
78
|
+
const date = new Date(year, month - 1, prevMonthLastDay.getDate() - i)
|
|
79
|
+
days.push(createDay(date, false, today, selected, minDate, maxDate))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Add days from current month
|
|
83
|
+
for (let d = 1; d <= lastDay.getDate(); d++) {
|
|
84
|
+
const date = new Date(year, month, d)
|
|
85
|
+
days.push(createDay(date, true, today, selected, minDate, maxDate))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add days from next month to fill the grid
|
|
89
|
+
const remaining = 42 - days.length // 6 rows of 7 days
|
|
90
|
+
for (let d = 1; d <= remaining; d++) {
|
|
91
|
+
const date = new Date(year, month + 1, d)
|
|
92
|
+
days.push(createDay(date, false, today, selected, minDate, maxDate))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return days
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createDay(
|
|
99
|
+
date: Date,
|
|
100
|
+
isCurrentMonth: boolean,
|
|
101
|
+
today: Date,
|
|
102
|
+
selected: { start: Date | null; end: Date | null },
|
|
103
|
+
minDate?: Date,
|
|
104
|
+
maxDate?: Date
|
|
105
|
+
): CalendarDay {
|
|
106
|
+
const isStart = selected.start ? isSameDay(date, selected.start) : false
|
|
107
|
+
const isEnd = selected.end ? isSameDay(date, selected.end) : false
|
|
108
|
+
const inRange = isInRange(date, selected.start, selected.end)
|
|
109
|
+
|
|
110
|
+
let isDisabled = false
|
|
111
|
+
if (minDate && date < minDate) isDisabled = true
|
|
112
|
+
if (maxDate && date > maxDate) isDisabled = true
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
date,
|
|
116
|
+
isCurrentMonth,
|
|
117
|
+
isToday: isSameDay(date, today),
|
|
118
|
+
isSelected: isStart || isEnd,
|
|
119
|
+
isInRange: inRange && !isStart && !isEnd,
|
|
120
|
+
isRangeStart: isStart,
|
|
121
|
+
isRangeEnd: isEnd,
|
|
122
|
+
isDisabled,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Calendar({
|
|
127
|
+
selected = { start: null, end: null },
|
|
128
|
+
onSelect,
|
|
129
|
+
mode = 'range',
|
|
130
|
+
minDate,
|
|
131
|
+
maxDate,
|
|
132
|
+
className,
|
|
133
|
+
}: CalendarProps) {
|
|
134
|
+
const [viewDate, setViewDate] = React.useState(() => {
|
|
135
|
+
if (selected.start) return new Date(selected.start)
|
|
136
|
+
return new Date()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const [rangeSelection, setRangeSelection] = React.useState<{
|
|
140
|
+
start: Date | null
|
|
141
|
+
end: Date | null
|
|
142
|
+
}>(selected)
|
|
143
|
+
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
setRangeSelection(selected)
|
|
146
|
+
}, [selected])
|
|
147
|
+
|
|
148
|
+
const year = viewDate.getFullYear()
|
|
149
|
+
const month = viewDate.getMonth()
|
|
150
|
+
const days = getCalendarDays(year, month, rangeSelection, minDate, maxDate)
|
|
151
|
+
|
|
152
|
+
const goToPreviousMonth = () => {
|
|
153
|
+
setViewDate(new Date(year, month - 1, 1))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const goToNextMonth = () => {
|
|
157
|
+
setViewDate(new Date(year, month + 1, 1))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const handleDayClick = (day: CalendarDay) => {
|
|
161
|
+
if (day.isDisabled) return
|
|
162
|
+
|
|
163
|
+
if (mode === 'single') {
|
|
164
|
+
const newSelection = { start: day.date, end: day.date }
|
|
165
|
+
setRangeSelection(newSelection)
|
|
166
|
+
onSelect?.(newSelection)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Range mode
|
|
171
|
+
if (!rangeSelection.start || (rangeSelection.start && rangeSelection.end)) {
|
|
172
|
+
// Start new range
|
|
173
|
+
const newSelection = { start: day.date, end: null }
|
|
174
|
+
setRangeSelection(newSelection)
|
|
175
|
+
onSelect?.(newSelection as { start: Date; end: Date | null })
|
|
176
|
+
} else {
|
|
177
|
+
// Complete range
|
|
178
|
+
let start = rangeSelection.start
|
|
179
|
+
let end = day.date
|
|
180
|
+
|
|
181
|
+
// Ensure start is before end
|
|
182
|
+
if (end < start) {
|
|
183
|
+
;[start, end] = [end, start]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const newSelection = { start, end }
|
|
187
|
+
setRangeSelection(newSelection)
|
|
188
|
+
onSelect?.(newSelection)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<div data-slot="calendar" className={cn('w-[280px] p-3', className)}>
|
|
194
|
+
{/* Header */}
|
|
195
|
+
<div className="mb-2 flex items-center justify-between">
|
|
196
|
+
<button
|
|
197
|
+
type="button"
|
|
198
|
+
onClick={goToPreviousMonth}
|
|
199
|
+
className="hover:bg-accent inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors"
|
|
200
|
+
aria-label="Previous month"
|
|
201
|
+
>
|
|
202
|
+
<ChevronLeft className="h-4 w-4" />
|
|
203
|
+
</button>
|
|
204
|
+
<span className="text-sm font-medium">
|
|
205
|
+
{MONTHS[month]} {year}
|
|
206
|
+
</span>
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
onClick={goToNextMonth}
|
|
210
|
+
className="hover:bg-accent inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors"
|
|
211
|
+
aria-label="Next month"
|
|
212
|
+
>
|
|
213
|
+
<ChevronRight className="h-4 w-4" />
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Weekday headers */}
|
|
218
|
+
<div className="mb-1 grid grid-cols-7 gap-1">
|
|
219
|
+
{WEEKDAYS.map((day) => (
|
|
220
|
+
<div
|
|
221
|
+
key={day}
|
|
222
|
+
className="text-muted-foreground flex h-8 w-8 items-center justify-center text-center text-xs font-medium"
|
|
223
|
+
>
|
|
224
|
+
{day}
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{/* Days grid */}
|
|
230
|
+
<div className="grid grid-cols-7 gap-1">
|
|
231
|
+
{days.map((day, i) => (
|
|
232
|
+
<button
|
|
233
|
+
key={i}
|
|
234
|
+
type="button"
|
|
235
|
+
disabled={day.isDisabled}
|
|
236
|
+
onClick={() => handleDayClick(day)}
|
|
237
|
+
className={cn(
|
|
238
|
+
'inline-flex h-8 w-8 items-center justify-center rounded-md text-sm transition-colors',
|
|
239
|
+
// Base states
|
|
240
|
+
!day.isCurrentMonth && 'text-muted-foreground/50',
|
|
241
|
+
day.isDisabled && 'cursor-not-allowed opacity-30',
|
|
242
|
+
!day.isDisabled && !day.isSelected && 'hover:bg-accent',
|
|
243
|
+
// Today
|
|
244
|
+
day.isToday && !day.isSelected && 'border-primary border',
|
|
245
|
+
// Selected states
|
|
246
|
+
day.isSelected && 'bg-primary text-primary-foreground',
|
|
247
|
+
day.isRangeStart && 'rounded-r-none',
|
|
248
|
+
day.isRangeEnd && 'rounded-l-none',
|
|
249
|
+
// In range
|
|
250
|
+
day.isInRange && 'bg-primary/20 rounded-none'
|
|
251
|
+
)}
|
|
252
|
+
>
|
|
253
|
+
{day.date.getDate()}
|
|
254
|
+
</button>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
Calendar.displayName = 'Calendar'
|
|
261
|
+
|
|
262
|
+
export { Calendar }
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
TimeRangePicker,
|
|
5
|
+
type TimeRange,
|
|
6
|
+
type DateRangePreset,
|
|
7
|
+
} from './time-range-picker'
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof TimeRangePicker> = {
|
|
10
|
+
title: 'UI/TimeRangePicker',
|
|
11
|
+
component: TimeRangePicker,
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
parameters: {
|
|
14
|
+
layout: 'centered',
|
|
15
|
+
},
|
|
16
|
+
decorators: [
|
|
17
|
+
(Story) => (
|
|
18
|
+
<div className="gram-elements bg-background text-foreground min-w-[400px] p-8">
|
|
19
|
+
<Story />
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
argTypes: {
|
|
24
|
+
showLive: {
|
|
25
|
+
control: 'boolean',
|
|
26
|
+
},
|
|
27
|
+
disabled: {
|
|
28
|
+
control: 'boolean',
|
|
29
|
+
},
|
|
30
|
+
timezone: {
|
|
31
|
+
control: 'text',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default meta
|
|
37
|
+
type Story = StoryObj<typeof TimeRangePicker>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default time range picker with preset badges and calendar.
|
|
41
|
+
* Supports natural language input with AI parsing.
|
|
42
|
+
*/
|
|
43
|
+
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
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Time range picker with timezone indicator.
|
|
72
|
+
*/
|
|
73
|
+
export const WithTimezone: Story = {
|
|
74
|
+
render: () => {
|
|
75
|
+
const [preset, setPreset] = useState<DateRangePreset | null>('30d')
|
|
76
|
+
const [customRange, setCustomRange] = useState<TimeRange | null>(null)
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<TimeRangePicker
|
|
80
|
+
preset={customRange ? null : preset}
|
|
81
|
+
customRange={customRange}
|
|
82
|
+
onPresetChange={(p) => {
|
|
83
|
+
setPreset(p)
|
|
84
|
+
setCustomRange(null)
|
|
85
|
+
}}
|
|
86
|
+
onCustomRangeChange={(from, to) => {
|
|
87
|
+
setCustomRange({ from, to })
|
|
88
|
+
setPreset(null)
|
|
89
|
+
}}
|
|
90
|
+
onClearCustomRange={() => {
|
|
91
|
+
setCustomRange(null)
|
|
92
|
+
setPreset('30d')
|
|
93
|
+
}}
|
|
94
|
+
timezone="UTC-08:00"
|
|
95
|
+
/>
|
|
96
|
+
)
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* With LIVE mode toggle enabled.
|
|
102
|
+
*/
|
|
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)
|
|
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
|
+
},
|
|
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="text-muted-foreground bg-muted rounded-md p-3 text-xs">
|
|
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
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Natural language parsing demo.
|
|
207
|
+
* Type things like:
|
|
208
|
+
* - "yesterday"
|
|
209
|
+
* - "3 days ago"
|
|
210
|
+
* - "last Wednesday"
|
|
211
|
+
* - "past 2 weeks"
|
|
212
|
+
* - "January 2024"
|
|
213
|
+
*/
|
|
214
|
+
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-muted-foreground text-sm">
|
|
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
|
+
},
|
|
249
|
+
}
|