feat: add ButtonGroup primitive and redesign TopBar time range selector
All checks were successful
Build & Publish / publish (push) Successful in 46s
All checks were successful
Build & Publish / publish (push) Successful in 46s
Replace the TimeRangeDropdown popover with inline FilterPills inside a new ButtonGroup component. The ButtonGroup merges adjacent children into a single visual strip with shared borders and rounded end-caps. The time readout is now an integrated inset display cell at the right end of the group. Preset ranges show "HH:MM – now"; custom ranges show both timestamps. Default changed from 3h to 1h. TopBar reordered to: Breadcrumb | Search | Status pills | Time pills | Right. FilterPill upgraded to forwardRef with data-active attribute. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Center search trigger */
|
/* Search trigger */
|
||||||
.search {
|
.search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.15s;
|
transition: border-color 0.15s;
|
||||||
min-width: 180px;
|
width: 200px;
|
||||||
flex: 1;
|
flex-shrink: 1;
|
||||||
max-width: 280px;
|
min-width: 120px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,23 +39,7 @@ export function TopBar({
|
|||||||
{/* Left: Breadcrumb */}
|
{/* Left: Breadcrumb */}
|
||||||
<Breadcrumb items={breadcrumb} className={styles.breadcrumb} />
|
<Breadcrumb items={breadcrumb} className={styles.breadcrumb} />
|
||||||
|
|
||||||
{/* Filters: time range + status pills */}
|
{/* Search trigger */}
|
||||||
<div className={styles.filters}>
|
|
||||||
<TimeRangeDropdown
|
|
||||||
value={globalFilters.timeRange}
|
|
||||||
onChange={globalFilters.setTimeRange}
|
|
||||||
/>
|
|
||||||
{STATUS_PILLS.map(({ status, label }) => (
|
|
||||||
<FilterPill
|
|
||||||
key={status}
|
|
||||||
label={label}
|
|
||||||
active={globalFilters.statusFilters.has(status)}
|
|
||||||
onClick={() => globalFilters.toggleStatus(status)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Center: Search trigger */}
|
|
||||||
<button
|
<button
|
||||||
className={styles.search}
|
className={styles.search}
|
||||||
onClick={() => commandPalette.setOpen(true)}
|
onClick={() => commandPalette.setOpen(true)}
|
||||||
@@ -72,6 +56,24 @@ export function TopBar({
|
|||||||
<span className={styles.kbd}>Ctrl+K</span>
|
<span className={styles.kbd}>Ctrl+K</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Status pills */}
|
||||||
|
<div className={styles.filters}>
|
||||||
|
{STATUS_PILLS.map(({ status, label }) => (
|
||||||
|
<FilterPill
|
||||||
|
key={status}
|
||||||
|
label={label}
|
||||||
|
active={globalFilters.statusFilters.has(status)}
|
||||||
|
onClick={() => globalFilters.toggleStatus(status)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Time range pills */}
|
||||||
|
<TimeRangeDropdown
|
||||||
|
value={globalFilters.timeRange}
|
||||||
|
onChange={globalFilters.setTimeRange}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Right: env badge, user */}
|
{/* Right: env badge, user */}
|
||||||
<div className={styles.right}>
|
<div className={styles.right}>
|
||||||
{environment && (
|
{environment && (
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.group {
|
||||||
|
display: inline-flex;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horizontal (default) */
|
||||||
|
.horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal > :global(*) {
|
||||||
|
border-radius: 0;
|
||||||
|
margin-left: -1px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal > :global(*:first-child) {
|
||||||
|
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal > :global(*:last-child) {
|
||||||
|
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal > :global(*:only-child) {
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vertical */
|
||||||
|
.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical > :global(*) {
|
||||||
|
border-radius: 0;
|
||||||
|
margin-top: -1px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical > :global(*:first-child) {
|
||||||
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical > :global(*:last-child) {
|
||||||
|
border-radius: 0 0 var(--radius-sm) var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical > :global(*:only-child) {
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active/hovered items sit above siblings so their borders win */
|
||||||
|
.group > :global(*:hover),
|
||||||
|
.group > :global(*:focus-visible),
|
||||||
|
.group > :global(*[data-active="true"]),
|
||||||
|
.group > :global(*.active) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
23
src/design-system/primitives/ButtonGroup/ButtonGroup.tsx
Normal file
23
src/design-system/primitives/ButtonGroup/ButtonGroup.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { type ReactNode } from 'react'
|
||||||
|
import styles from './ButtonGroup.module.css'
|
||||||
|
|
||||||
|
interface ButtonGroupProps {
|
||||||
|
children: ReactNode
|
||||||
|
orientation?: 'horizontal' | 'vertical'
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonGroup({
|
||||||
|
children,
|
||||||
|
orientation = 'horizontal',
|
||||||
|
className,
|
||||||
|
}: ButtonGroupProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles.group} ${styles[orientation]} ${className ?? ''}`}
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { forwardRef } from 'react'
|
||||||
import styles from './FilterPill.module.css'
|
import styles from './FilterPill.module.css'
|
||||||
|
|
||||||
interface FilterPillProps {
|
interface FilterPillProps {
|
||||||
@@ -10,7 +11,8 @@ interface FilterPillProps {
|
|||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilterPill({
|
export const FilterPill = forwardRef<HTMLButtonElement, FilterPillProps>(
|
||||||
|
({
|
||||||
label,
|
label,
|
||||||
count,
|
count,
|
||||||
active = false,
|
active = false,
|
||||||
@@ -18,7 +20,7 @@ export function FilterPill({
|
|||||||
dotColor,
|
dotColor,
|
||||||
onClick,
|
onClick,
|
||||||
className,
|
className,
|
||||||
}: FilterPillProps) {
|
}, ref) => {
|
||||||
const classes = [
|
const classes = [
|
||||||
styles.pill,
|
styles.pill,
|
||||||
active ? styles.active : '',
|
active ? styles.active : '',
|
||||||
@@ -26,7 +28,7 @@ export function FilterPill({
|
|||||||
].filter(Boolean).join(' ')
|
].filter(Boolean).join(' ')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={classes} onClick={onClick} type="button">
|
<button ref={ref} className={classes} onClick={onClick} type="button" data-active={active || undefined}>
|
||||||
{dot && (
|
{dot && (
|
||||||
<span
|
<span
|
||||||
className={styles.dot}
|
className={styles.dot}
|
||||||
@@ -39,4 +41,7 @@ export function FilterPill({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
FilterPill.displayName = 'FilterPill'
|
||||||
|
|||||||
@@ -1,45 +1,78 @@
|
|||||||
.trigger {
|
/* ── Integrated readout cell ──────────────────────────────
|
||||||
display: flex;
|
First child of the ButtonGroup — styled as a recessed
|
||||||
|
instrument-panel display, not a clickable control. */
|
||||||
|
|
||||||
|
.readout {
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
padding: 4px 12px;
|
||||||
padding: 4px 10px;
|
|
||||||
height: 28px;
|
height: 28px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius-sm);
|
background: var(--bg-inset);
|
||||||
background: var(--bg-raised);
|
box-shadow: inset 0 1px 3px rgba(44, 37, 32, 0.06);
|
||||||
color: var(--amber, var(--warning));
|
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: default;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .readout {
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Custom date picker panel ────────────────────────── */
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
min-width: 220px;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
z-index: 500;
|
||||||
|
animation: panelIn 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes panelIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.applyBtn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 5px 14px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--amber);
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--font-body);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.15s, background 0.15s;
|
transition: opacity 0.15s;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.trigger:hover {
|
.applyBtn:hover {
|
||||||
border-color: var(--text-faint);
|
opacity: 0.85;
|
||||||
background: var(--bg-surface);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.applyBtn:disabled {
|
||||||
font-size: 13px;
|
opacity: 0.4;
|
||||||
line-height: 1;
|
cursor: not-allowed;
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caret {
|
|
||||||
font-size: 9px;
|
|
||||||
opacity: 0.7;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presetList {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,46 @@
|
|||||||
|
import { useState, useRef, useEffect, useCallback, useMemo } from 'react'
|
||||||
|
import { createPortal } from 'react-dom'
|
||||||
import styles from './TimeRangeDropdown.module.css'
|
import styles from './TimeRangeDropdown.module.css'
|
||||||
import { Popover } from '../../composites/Popover/Popover'
|
|
||||||
import { FilterPill } from '../FilterPill/FilterPill'
|
import { FilterPill } from '../FilterPill/FilterPill'
|
||||||
import { computePresetRange, PRESET_SHORT_LABELS } from '../../utils/timePresets'
|
import { ButtonGroup } from '../ButtonGroup/ButtonGroup'
|
||||||
|
import { DateTimePicker } from '../DateTimePicker/DateTimePicker'
|
||||||
|
import { computePresetRange } from '../../utils/timePresets'
|
||||||
import type { TimeRange } from '../../providers/GlobalFilterProvider'
|
import type { TimeRange } from '../../providers/GlobalFilterProvider'
|
||||||
|
|
||||||
const DROPDOWN_PRESETS = [
|
function formatRangeLabel(range: TimeRange): string {
|
||||||
|
const start = range.preset ? computePresetRange(range.preset).start : range.start
|
||||||
|
|
||||||
|
const time = (d: Date) =>
|
||||||
|
d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||||
|
const dateTime = (d: Date) =>
|
||||||
|
d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + '\u2009' + time(d)
|
||||||
|
|
||||||
|
// Preset ranges are open-ended ("since X"), so only show the start
|
||||||
|
if (range.preset) {
|
||||||
|
const now = new Date()
|
||||||
|
const sameDay =
|
||||||
|
start.getFullYear() === now.getFullYear() &&
|
||||||
|
start.getMonth() === now.getMonth() &&
|
||||||
|
start.getDate() === now.getDate()
|
||||||
|
return sameDay ? `${time(start)}\u2009\u2013\u2009now` : `${dateTime(start)}\u2009\u2013\u2009now`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom range: show both ends
|
||||||
|
const end = range.end
|
||||||
|
const sameDay =
|
||||||
|
start.getFullYear() === end.getFullYear() &&
|
||||||
|
start.getMonth() === end.getMonth() &&
|
||||||
|
start.getDate() === end.getDate()
|
||||||
|
|
||||||
|
if (sameDay) return `${time(start)}\u2009\u2013\u2009${time(end)}`
|
||||||
|
return `${dateTime(start)}\u2009\u2013\u2009${dateTime(end)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRESETS = [
|
||||||
{ value: 'last-1h', label: '1h' },
|
{ value: 'last-1h', label: '1h' },
|
||||||
{ value: 'last-3h', label: '3h' },
|
{ value: 'last-3h', label: '3h' },
|
||||||
{ value: 'last-6h', label: '6h' },
|
{ value: 'last-6h', label: '6h' },
|
||||||
{ value: 'today', label: 'Today' },
|
{ value: 'today', label: 'Today' },
|
||||||
{ value: 'shift', label: 'Shift' },
|
|
||||||
{ value: 'last-24h', label: '24h' },
|
{ value: 'last-24h', label: '24h' },
|
||||||
{ value: 'last-7d', label: '7d' },
|
{ value: 'last-7d', label: '7d' },
|
||||||
]
|
]
|
||||||
@@ -21,35 +52,112 @@ interface TimeRangeDropdownProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TimeRangeDropdown({ value, onChange, className }: TimeRangeDropdownProps) {
|
export function TimeRangeDropdown({ value, onChange, className }: TimeRangeDropdownProps) {
|
||||||
const activeLabel = value.preset ? (PRESET_SHORT_LABELS[value.preset] ?? value.preset) : 'Custom'
|
const [open, setOpen] = useState(false)
|
||||||
|
const [customFrom, setCustomFrom] = useState<Date | null>(value.start)
|
||||||
|
const [customTo, setCustomTo] = useState<Date | null>(value.end)
|
||||||
|
const customRef = useRef<HTMLButtonElement>(null)
|
||||||
|
const panelRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [panelPos, setPanelPos] = useState({ top: 0, left: 0 })
|
||||||
|
const isCustom = value.preset === null || value.preset === 'custom'
|
||||||
|
|
||||||
|
const reposition = useCallback(() => {
|
||||||
|
if (!customRef.current) return
|
||||||
|
const rect = customRef.current.getBoundingClientRect()
|
||||||
|
const panelWidth = panelRef.current?.offsetWidth ?? 240
|
||||||
|
setPanelPos({
|
||||||
|
top: rect.bottom + window.scrollY + 8,
|
||||||
|
left: rect.right + window.scrollX - panelWidth,
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
const id = requestAnimationFrame(reposition)
|
||||||
|
return () => cancelAnimationFrame(id)
|
||||||
|
}
|
||||||
|
}, [open, reposition])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
function handleMouseDown(e: MouseEvent) {
|
||||||
|
if (
|
||||||
|
customRef.current?.contains(e.target as Node) ||
|
||||||
|
panelRef.current?.contains(e.target as Node)
|
||||||
|
) return
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
function handleKey(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') setOpen(false)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', handleMouseDown)
|
||||||
|
document.addEventListener('keydown', handleKey)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleMouseDown)
|
||||||
|
document.removeEventListener('keydown', handleKey)
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
const rangeLabel = useMemo(() => formatRangeLabel(value), [value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<>
|
||||||
className={className}
|
<ButtonGroup className={className}>
|
||||||
position="bottom"
|
{PRESETS.map((preset) => (
|
||||||
align="start"
|
|
||||||
trigger={
|
|
||||||
<button className={styles.trigger} type="button" aria-label="Select time range">
|
|
||||||
<span className={styles.icon} aria-hidden="true">⏱</span>
|
|
||||||
<span className={styles.label}>{activeLabel}</span>
|
|
||||||
<span className={styles.caret} aria-hidden="true">▾</span>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
content={
|
|
||||||
<div className={styles.presetList}>
|
|
||||||
{DROPDOWN_PRESETS.map((preset) => (
|
|
||||||
<FilterPill
|
<FilterPill
|
||||||
key={preset.value}
|
key={preset.value}
|
||||||
label={preset.label}
|
label={preset.label}
|
||||||
active={value.preset === preset.value}
|
active={value.preset === preset.value}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setOpen(false)
|
||||||
const range = computePresetRange(preset.value)
|
const range = computePresetRange(preset.value)
|
||||||
onChange({ ...range, preset: preset.value })
|
onChange({ ...range, preset: preset.value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
<FilterPill
|
||||||
}
|
ref={customRef}
|
||||||
|
label="Custom"
|
||||||
|
active={isCustom}
|
||||||
|
onClick={() => setOpen((prev) => !prev)}
|
||||||
/>
|
/>
|
||||||
|
<span className={styles.readout} aria-label="Active time range">
|
||||||
|
{rangeLabel}
|
||||||
|
</span>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{open && createPortal(
|
||||||
|
<div
|
||||||
|
ref={panelRef}
|
||||||
|
className={styles.panel}
|
||||||
|
style={{ top: panelPos.top, left: panelPos.left }}
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<DateTimePicker
|
||||||
|
label="From"
|
||||||
|
value={customFrom ?? undefined}
|
||||||
|
onChange={(d) => setCustomFrom(d)}
|
||||||
|
/>
|
||||||
|
<DateTimePicker
|
||||||
|
label="To"
|
||||||
|
value={customTo ?? undefined}
|
||||||
|
onChange={(d) => setCustomTo(d)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.applyBtn}
|
||||||
|
disabled={!customFrom || !customTo}
|
||||||
|
onClick={() => {
|
||||||
|
if (customFrom && customTo) {
|
||||||
|
onChange({ start: customFrom, end: customTo, preset: null })
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export { Alert } from './Alert/Alert'
|
|||||||
export { Avatar } from './Avatar/Avatar'
|
export { Avatar } from './Avatar/Avatar'
|
||||||
export { Badge } from './Badge/Badge'
|
export { Badge } from './Badge/Badge'
|
||||||
export { Button } from './Button/Button'
|
export { Button } from './Button/Button'
|
||||||
|
export { ButtonGroup } from './ButtonGroup/ButtonGroup'
|
||||||
export { Card } from './Card/Card'
|
export { Card } from './Card/Card'
|
||||||
export { Checkbox } from './Checkbox/Checkbox'
|
export { Checkbox } from './Checkbox/Checkbox'
|
||||||
export { CodeBlock } from './CodeBlock/CodeBlock'
|
export { CodeBlock } from './CodeBlock/CodeBlock'
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface GlobalFilterContextValue {
|
|||||||
|
|
||||||
const GlobalFilterContext = createContext<GlobalFilterContextValue | null>(null)
|
const GlobalFilterContext = createContext<GlobalFilterContextValue | null>(null)
|
||||||
|
|
||||||
const DEFAULT_PRESET = 'last-3h'
|
const DEFAULT_PRESET = 'last-1h'
|
||||||
|
|
||||||
function getDefaultTimeRange(): TimeRange {
|
function getDefaultTimeRange(): TimeRange {
|
||||||
const { start, end } = computePresetRange(DEFAULT_PRESET)
|
const { start, end } = computePresetRange(DEFAULT_PRESET)
|
||||||
|
|||||||
Reference in New Issue
Block a user