segmented buttons style

This commit is contained in:
2026-07-08 14:48:50 +05:00
parent 9a542ce33e
commit 1127b14946
7 changed files with 85 additions and 38 deletions

View File

@@ -4,6 +4,8 @@ import { useRef } from 'react';
import { CalendarDays, CalendarClock, Check, ChevronDown, Clock3, DatabaseZap, Search } from 'lucide-react'; import { CalendarDays, CalendarClock, Check, ChevronDown, Clock3, DatabaseZap, Search } from 'lucide-react';
import type { CurrentItem } from '../../../entities/current/types'; import type { CurrentItem } from '../../../entities/current/types';
import { HistoryChart } from '../../../features/history-chart/HistoryChart'; import { HistoryChart } from '../../../features/history-chart/HistoryChart';
import { HistoryChartAvgLineModeControl } from '../../../features/history-chart/HistoryChartAvgLineModeControl';
import type { AvgLineMode } from '../../../features/history-chart/chartTypes';
import { RANGE_PRESETS, createRange, type DateRangeState } from '../../../features/history/dateRange'; import { RANGE_PRESETS, createRange, type DateRangeState } from '../../../features/history/dateRange';
import { formatNumber, toIsoFromInput } from '../../../utils/format'; import { formatNumber, toIsoFromInput } from '../../../utils/format';
import type { HistoryGranularity } from '../../../utils/historyGranularity'; import type { HistoryGranularity } from '../../../utils/historyGranularity';
@@ -93,6 +95,7 @@ export function ArchiveView({
onSelectVisibleTags, onSelectVisibleTags,
onToggleTag, onToggleTag,
}: ArchiveViewProps) { }: ArchiveViewProps) {
const [avgLineMode, setAvgLineMode] = useState<AvgLineMode>('auto');
const [selectorOpen, setSelectorOpen] = useState(false); const [selectorOpen, setSelectorOpen] = useState(false);
const selectedPreview = selectedTags.slice(0, 10); const selectedPreview = selectedTags.slice(0, 10);
const hiddenSelectedCount = Math.max(0, selectedTags.length - selectedPreview.length); const hiddenSelectedCount = Math.max(0, selectedTags.length - selectedPreview.length);
@@ -209,7 +212,7 @@ export function ArchiveView({
onPointerEnter={() => setSelectorOpen(false)} onPointerEnter={() => setSelectorOpen(false)}
> >
<div className="toolbar"> <div className="toolbar">
<div className="segmented"> <div className="segmented segmented--range-preset">
{RANGE_PRESETS.map((preset) => ( {RANGE_PRESETS.map((preset) => (
<button key={preset.id} type="button" onClick={() => onRangeChange(createRange(preset.hours))}> <button key={preset.id} type="button" onClick={() => onRangeChange(createRange(preset.hours))}>
{preset.label} {preset.label}
@@ -250,11 +253,13 @@ export function ArchiveView({
onChange={(value) => updateRangePart('to', 'time', value)} onChange={(value) => updateRangePart('to', 'time', value)}
/> />
</div> </div>
<HistoryChartAvgLineModeControl value={avgLineMode} onChange={setAvgLineMode} />
</div> </div>
{selectedTags.length ? ( {selectedTags.length ? (
<HistoryChart <HistoryChart
key={`${range.from}:${range.to}:${historyAxis.granulate}`} key={`${range.from}:${range.to}:${historyAxis.granulate}:${historyAxis.tickIntervalMs ?? ''}`}
avgLineMode={avgLineMode}
edge={edgeId} edge={edgeId}
from={fromIso} from={fromIso}
to={toIso} to={toIso}

View File

@@ -16,6 +16,7 @@ import { useHistoryChartQueries } from './useHistoryChartQueries';
import { useHistoryChartSeries } from './useHistoryChartSeries'; import { useHistoryChartSeries } from './useHistoryChartSeries';
type HistoryChartProps = { type HistoryChartProps = {
avgLineMode: AvgLineMode;
edge: string; edge: string;
from: string; from: string;
granulate: string; granulate: string;
@@ -42,6 +43,7 @@ function shouldShowAvgLine(mode: AvgLineMode, range: HistoryZoomRange): boolean
} }
export function HistoryChart({ export function HistoryChart({
avgLineMode,
edge, edge,
from, from,
granulate, granulate,
@@ -62,7 +64,6 @@ export function HistoryChart({
const zoomRangeRef = useRef(zoomRange); const zoomRangeRef = useRef(zoomRange);
const zoomTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const zoomTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastReadyOptionRef = useRef<EChartsOption | null>(null); const lastReadyOptionRef = useRef<EChartsOption | null>(null);
const [avgLineMode, setAvgLineMode] = useState<AvgLineMode>('auto');
const lines = useHistoryChartQueries({ const lines = useHistoryChartQueries({
edge, edge,
from: zoomRange.from, from: zoomRange.from,
@@ -149,13 +150,12 @@ export function HistoryChart({
return ( return (
<HistoryChartArea <HistoryChartArea
avgLineMode={avgLineMode}
hasData={displayHasData} hasData={displayHasData}
hasSelection={tags.length > 0} hasSelection={tags.length > 0}
loading={loading} loading={loading}
avgLineMode={avgLineMode}
option={displayOption} option={displayOption}
onDataZoom={handleDataZoom} onDataZoom={handleDataZoom}
onAvgLineModeChange={setAvgLineMode}
/> />
); );
} }

View File

@@ -2,8 +2,9 @@ import type { EChartsOption } from 'echarts';
import ReactEChartsCore from 'echarts-for-react/esm/core.js'; import ReactEChartsCore from 'echarts-for-react/esm/core.js';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { WheelEvent } from 'react'; import type { WheelEvent } from 'react';
import type { AvgLineMode, DataZoomEventBatch } from './chartTypes'; import type { DataZoomEventBatch } from './chartTypes';
import { echarts } from './historyChartEcharts'; import { echarts } from './historyChartEcharts';
import type { AvgLineMode } from './chartTypes';
type HistoryChartAreaProps = { type HistoryChartAreaProps = {
avgLineMode: AvgLineMode; avgLineMode: AvgLineMode;
@@ -11,17 +12,9 @@ type HistoryChartAreaProps = {
hasSelection: boolean; hasSelection: boolean;
loading: boolean; loading: boolean;
option: EChartsOption; option: EChartsOption;
onAvgLineModeChange: (mode: AvgLineMode) => void;
onDataZoom: (event: DataZoomEventBatch) => void; onDataZoom: (event: DataZoomEventBatch) => void;
}; };
// Порядок режимов соответствует требованию: авто, принудительно рисовать, принудительно скрыть.
const AVG_LINE_MODES: Array<{ value: AvgLineMode; label: string }> = [
{ value: 'auto', label: 'Авто' },
{ value: 'show', label: 'Рисовать' },
{ value: 'hide', label: 'Не рисовать' },
];
/** Включает или выключает нативный X-зум колесом, чтобы Shift + колесо работал только по Y. */ /** Включает или выключает нативный X-зум колесом, чтобы Shift + колесо работал только по Y. */
function setXAxisWheelZoom(chart: ReturnType<ReactEChartsCore['getEchartsInstance']>, enabled: boolean): void { function setXAxisWheelZoom(chart: ReturnType<ReactEChartsCore['getEchartsInstance']>, enabled: boolean): void {
chart.setOption( chart.setOption(
@@ -44,7 +37,6 @@ export function HistoryChartArea({
hasSelection, hasSelection,
loading, loading,
option, option,
onAvgLineModeChange,
onDataZoom, onDataZoom,
}: HistoryChartAreaProps) { }: HistoryChartAreaProps) {
const chartRef = useRef<InstanceType<typeof ReactEChartsCore> | null>(null); const chartRef = useRef<InstanceType<typeof ReactEChartsCore> | null>(null);
@@ -111,22 +103,6 @@ export function HistoryChartArea({
return ( return (
<div className="history-chart-shell" onWheelCapture={handleWheelCapture}> <div className="history-chart-shell" onWheelCapture={handleWheelCapture}>
<div className="history-chart-controls" aria-label="Режим соединительной линии avg">
<span>Линия avg</span>
<div className="segmented history-chart-line-mode">
{AVG_LINE_MODES.map((mode) => (
<button
key={mode.value}
type="button"
className={mode.value === avgLineMode ? 'history-chart-line-mode__button--active' : undefined}
onClick={() => onAvgLineModeChange(mode.value)}
aria-pressed={mode.value === avgLineMode}
>
{mode.label}
</button>
))}
</div>
</div>
<ReactEChartsCore <ReactEChartsCore
key={avgLineMode} key={avgLineMode}
ref={chartRef} ref={chartRef}

View File

@@ -0,0 +1,33 @@
import type { AvgLineMode } from './chartTypes';
type HistoryChartAvgLineModeControlProps = {
value: AvgLineMode;
onChange: (mode: AvgLineMode) => void;
};
const AVG_LINE_MODES: Array<{ value: AvgLineMode; label: string }> = [
{ value: 'auto', label: 'Авто' },
{ value: 'show', label: 'Рисовать' },
{ value: 'hide', label: 'Не рисовать' },
];
export function HistoryChartAvgLineModeControl({ value, onChange }: HistoryChartAvgLineModeControlProps) {
return (
<div className="archive-avgline-mode-control" aria-label="Режим соединительной линии avg">
<span>Линия avg</span>
<div className="segmented history-chart-line-mode">
{AVG_LINE_MODES.map((mode) => (
<button
key={mode.value}
type="button"
className={mode.value === value ? 'history-chart-line-mode__button--active' : undefined}
onClick={() => onChange(mode.value)}
aria-pressed={mode.value === value}
>
{mode.label}
</button>
))}
</div>
</div>
);
}

View File

@@ -41,7 +41,7 @@ function createAvgLineData(points: HistoryPoint[], granulate: string, breakOnGap
} }
const previousTime = new Date(finitePoints[index - 1].time).getTime(); const previousTime = new Date(finitePoints[index - 1].time).getTime();
const hasAcceptableGap = currentTime - previousTime <= slotMs * 100; const hasAcceptableGap = currentTime - previousTime <= slotMs * 24;
return hasAcceptableGap ? [currentPoint] : [null, currentPoint]; return hasAcceptableGap ? [currentPoint] : [null, currentPoint];
}); });

View File

@@ -28,16 +28,41 @@
padding: 3px; padding: 3px;
} }
.archive-avgline-mode-control .history-chart-line-mode button {
border-radius: 0;
}
.archive-avgline-mode-control .history-chart-line-mode button:first-child {
border-top-left-radius: var(--radius);
border-bottom-left-radius: var(--radius);
}
.archive-avgline-mode-control .history-chart-line-mode button:last-child {
border-top-right-radius: var(--radius);
border-bottom-right-radius: var(--radius);
}
.segmented--range-preset button {
border-radius: 0;
}
.segmented--range-preset button:first-child {
border-top-left-radius: var(--radius);
border-bottom-left-radius: var(--radius);
}
.segmented--range-preset button:last-child {
border-top-right-radius: var(--radius);
border-bottom-right-radius: var(--radius);
}
.history-chart-line-mode button { .history-chart-line-mode button {
min-height: 24px; font-size: inherit;
padding: 0 8px;
font-size: 12px;
} }
.history-chart-line-mode__button--active { .history-chart-line-mode__button--active {
color: #f8fafc; border-color: var(--line-strong);
background: rgba(212, 165, 116, 0.16); background: rgba(201, 122, 61, 0.18);
border-color: rgba(212, 165, 116, 0.38);
} }
.chart-section--archive .history-chart-shell, .chart-section--archive .history-chart-shell,

View File

@@ -216,6 +216,14 @@
font-size: 0.84rem; font-size: 0.84rem;
} }
.archive-avgline-mode-control {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: 0.84rem;
}
.archive-date-input { .archive-date-input {
color-scheme: dark; color-scheme: dark;
min-height: 36px; min-height: 36px;