mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-27 14:10:30 +01:00
Compare commits
5 Commits
cancel-sub
...
chore/mino
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c78a85dd27 | ||
|
|
0ab5ac2e00 | ||
|
|
4311c3c14a | ||
|
|
efa71e46a3 | ||
|
|
c82748ce1a |
@@ -33,11 +33,13 @@ export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
}
|
||||
const tooltipProps: BarTooltipProps = {
|
||||
...props,
|
||||
id: config.getId(),
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
isStackedBarChart: isStackedBarChart,
|
||||
canPinTooltip: rest.canPinTooltip,
|
||||
renderTooltipFooter: rest.renderTooltipFooter,
|
||||
};
|
||||
return <BarChartTooltip {...tooltipProps} />;
|
||||
},
|
||||
@@ -48,6 +50,7 @@ export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
rest.decimalPrecision,
|
||||
isStackedBarChart,
|
||||
rest.canPinTooltip,
|
||||
rest.renderTooltipFooter,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -24,13 +24,21 @@ export default function Histogram(props: HistogramChartProps): JSX.Element {
|
||||
}
|
||||
const tooltipProps: HistogramTooltipProps = {
|
||||
...props,
|
||||
id: rest.config.getId(),
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
canPinTooltip: rest.canPinTooltip,
|
||||
renderTooltipFooter: rest.renderTooltipFooter,
|
||||
};
|
||||
return <HistogramTooltip {...tooltipProps} />;
|
||||
},
|
||||
[customTooltip, rest.yAxisUnit, rest.decimalPrecision, rest.canPinTooltip],
|
||||
[
|
||||
customTooltip,
|
||||
rest.yAxisUnit,
|
||||
rest.decimalPrecision,
|
||||
rest.canPinTooltip,
|
||||
rest.renderTooltipFooter,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { TimeSeriesChartProps } from '../types';
|
||||
|
||||
export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
const { children, customTooltip, pinnedTooltipElement, ...rest } = props;
|
||||
const { children, customTooltip, ...rest } = props;
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
@@ -18,10 +18,12 @@ export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
}
|
||||
const tooltipProps: TimeSeriesTooltipProps = {
|
||||
...props,
|
||||
id: rest.config.getId(),
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
canPinTooltip: rest.canPinTooltip,
|
||||
renderTooltipFooter: rest.renderTooltipFooter,
|
||||
};
|
||||
return <TimeSeriesTooltip {...tooltipProps} />;
|
||||
},
|
||||
@@ -31,15 +33,12 @@ export default function TimeSeries(props: TimeSeriesChartProps): JSX.Element {
|
||||
rest.yAxisUnit,
|
||||
rest.decimalPrecision,
|
||||
rest.canPinTooltip,
|
||||
rest.renderTooltipFooter,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<ChartWrapper
|
||||
{...rest}
|
||||
customTooltip={renderTooltip}
|
||||
pinnedTooltipElement={pinnedTooltipElement}
|
||||
>
|
||||
<ChartWrapper {...rest} customTooltip={renderTooltip}>
|
||||
{children}
|
||||
</ChartWrapper>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PrecisionOption } from 'components/Graph/types';
|
||||
import { LegendConfig, TooltipRenderArgs } from 'lib/uPlotV2/components/types';
|
||||
import {
|
||||
IRenderTooltipFooterArgs,
|
||||
LegendConfig,
|
||||
TooltipRenderArgs,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import {
|
||||
DashboardCursorSync,
|
||||
@@ -20,6 +24,7 @@ interface BaseChartProps {
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
pinnedTooltipElement?: (clickData: TooltipClickData) => React.ReactNode;
|
||||
renderTooltipFooter?: (args: IRenderTooltipFooterArgs) => React.ReactNode;
|
||||
customTooltip?: (props: TooltipRenderArgs) => React.ReactNode;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import {
|
||||
IRenderTooltipFooterArgs,
|
||||
LegendPosition,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import ContextMenu from 'periscope/components/ContextMenu';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import uPlot from 'uplot';
|
||||
@@ -14,6 +17,7 @@ import { usePanelContextMenu } from '../../hooks/usePanelContextMenu';
|
||||
import { prepareBarPanelConfig, prepareBarPanelData } from './utils';
|
||||
|
||||
import '../Panel.styles.scss';
|
||||
import TooltipFooter from '../components/TooltipFooter';
|
||||
|
||||
function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
const {
|
||||
@@ -113,6 +117,15 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
uPlotRef.current = plot;
|
||||
}, []);
|
||||
|
||||
const renderTooltipFooter = useCallback(
|
||||
({ isPinned, dismiss }: IRenderTooltipFooterArgs) => {
|
||||
return (
|
||||
<TooltipFooter id={widget.id} isPinned={isPinned} dismiss={dismiss} />
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="panel-container" ref={graphRef}>
|
||||
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
|
||||
@@ -132,6 +145,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
decimalPrecision={widget.decimalPrecision}
|
||||
timezone={timezone}
|
||||
renderTooltipFooter={renderTooltipFooter}
|
||||
>
|
||||
<ContextMenu
|
||||
coordinates={coordinates}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import {
|
||||
IRenderTooltipFooterArgs,
|
||||
LegendPosition,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import Histogram from '../../charts/Histogram/Histogram';
|
||||
@@ -13,6 +16,7 @@ import {
|
||||
} from './utils';
|
||||
|
||||
import '../Panel.styles.scss';
|
||||
import TooltipFooter from '../components/TooltipFooter';
|
||||
|
||||
function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
const {
|
||||
@@ -75,6 +79,20 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
widget.mergeAllActiveQueries,
|
||||
]);
|
||||
|
||||
const renderTooltipFooter = useCallback(
|
||||
({ isPinned, dismiss }: IRenderTooltipFooterArgs) => {
|
||||
return (
|
||||
<TooltipFooter
|
||||
id={widget.id}
|
||||
isPinned={isPinned}
|
||||
dismiss={dismiss}
|
||||
canDrilldown={false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="panel-container" ref={graphRef}>
|
||||
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
|
||||
@@ -97,6 +115,7 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
|
||||
width={containerDimensions.width}
|
||||
height={containerDimensions.height}
|
||||
layoutChildren={layoutChildren}
|
||||
renderTooltipFooter={renderTooltipFooter}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import TimeSeries from 'container/DashboardContainer/visualization/charts/TimeSeries/TimeSeries';
|
||||
import ChartManager from 'container/DashboardContainer/visualization/components/ChartManager/ChartManager';
|
||||
import { usePanelContextMenu } from 'container/DashboardContainer/visualization/hooks/usePanelContextMenu';
|
||||
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import {
|
||||
IRenderTooltipFooterArgs,
|
||||
LegendPosition,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
import { ContextMenu } from 'periscope/components/ContextMenu';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import uPlot from 'uplot';
|
||||
@@ -14,6 +17,7 @@ import { getTimeRange } from 'utils/getTimeRange';
|
||||
import { prepareChartData, prepareUPlotConfig } from '../TimeSeriesPanel/utils';
|
||||
|
||||
import '../Panel.styles.scss';
|
||||
import TooltipFooter from '../components/TooltipFooter';
|
||||
|
||||
function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
const {
|
||||
@@ -104,6 +108,15 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
widget.decimalPrecision,
|
||||
]);
|
||||
|
||||
const renderTooltipFooter = useCallback(
|
||||
({ isPinned, dismiss }: IRenderTooltipFooterArgs) => {
|
||||
return (
|
||||
<TooltipFooter id={widget.id} isPinned={isPinned} dismiss={dismiss} />
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="panel-container" ref={graphRef}>
|
||||
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
|
||||
@@ -120,6 +133,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
width={containerDimensions.width}
|
||||
height={containerDimensions.height}
|
||||
layoutChildren={layoutChildren}
|
||||
renderTooltipFooter={renderTooltipFooter}
|
||||
>
|
||||
<ContextMenu
|
||||
coordinates={coordinates}
|
||||
|
||||
@@ -7,22 +7,25 @@ import Styles from './TooltipFooter.module.scss';
|
||||
import { MousePointerClick } from '@signozhq/icons';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { Events } from 'constants/events';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
interface TooltipFooterProps {
|
||||
id: string;
|
||||
pinKey?: string;
|
||||
isPinned: boolean;
|
||||
canDrilldown?: boolean;
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
export default function TooltipFooter({
|
||||
id,
|
||||
pinKey = DEFAULT_PIN_TOOLTIP_KEY,
|
||||
isPinned,
|
||||
canDrilldown = true,
|
||||
dismiss,
|
||||
}: TooltipFooterProps): JSX.Element {
|
||||
const handleUnpinClick = (): void => {
|
||||
logEvent(Events.TOOLTIP_UNPINNED, {
|
||||
path: getAbsoluteUrl(window.location.pathname),
|
||||
id: id,
|
||||
});
|
||||
dismiss();
|
||||
};
|
||||
@@ -43,12 +46,14 @@ export default function TooltipFooter({
|
||||
</div>
|
||||
) : (
|
||||
<div className={Styles.hintList}>
|
||||
<div className={Styles.hint} data-active="false">
|
||||
<Kbd>
|
||||
<MousePointerClick size={12} />
|
||||
</Kbd>
|
||||
<span>Click to drilldown</span>
|
||||
</div>
|
||||
{canDrilldown && (
|
||||
<div className={Styles.hint} data-active="false">
|
||||
<Kbd>
|
||||
<MousePointerClick size={12} />
|
||||
</Kbd>
|
||||
<span>Click to drilldown</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={Styles.hint} data-active="false">
|
||||
<span>Press</span>
|
||||
<Kbd>{pinKey.toUpperCase()}</Kbd>
|
||||
@@ -8,15 +8,15 @@
|
||||
border: 1px solid var(--l2-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
&.pinned {
|
||||
border-color: var(--ring);
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--l2-border);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--l2-border);
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ import { useMemo } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { TooltipProps } from '../types';
|
||||
import TooltipFooter from './components/TooltipFooter/TooltipFooter';
|
||||
import TooltipHeader from './components/TooltipHeader/TooltipHeader';
|
||||
import TooltipList from './components/TooltipList/TooltipList';
|
||||
|
||||
import Styles from './Tooltip.module.scss';
|
||||
|
||||
export default function Tooltip({
|
||||
id,
|
||||
uPlotInstance,
|
||||
timezone,
|
||||
content,
|
||||
showTooltipHeader = true,
|
||||
isPinned,
|
||||
canPinTooltip,
|
||||
renderTooltipFooter,
|
||||
dismiss,
|
||||
}: TooltipProps): JSX.Element {
|
||||
const tooltipContent = useMemo(() => content ?? [], [content]);
|
||||
@@ -31,7 +31,9 @@ export default function Tooltip({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(Styles.container, isPinned && Styles.pinned)}
|
||||
className={cx(Styles.container, {
|
||||
[Styles.pinned]: isPinned,
|
||||
})}
|
||||
data-testid="uplot-tooltip-container"
|
||||
>
|
||||
{showHeader && (
|
||||
@@ -46,9 +48,9 @@ export default function Tooltip({
|
||||
|
||||
{showDivider && <span className={Styles.divider} />}
|
||||
|
||||
{showList && <TooltipList content={tooltipContent} />}
|
||||
{showList && <TooltipList id={id} content={tooltipContent} />}
|
||||
|
||||
{canPinTooltip && <TooltipFooter isPinned={isPinned} dismiss={dismiss} />}
|
||||
{renderTooltipFooter && renderTooltipFooter({ isPinned, dismiss })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { render, RenderResult, screen } from 'tests/test-utils';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { TooltipContentItem } from '../../types';
|
||||
import { IRenderTooltipFooterArgs, TooltipContentItem } from '../../types';
|
||||
import Tooltip from '../Tooltip';
|
||||
|
||||
type MockVirtuosoProps = {
|
||||
@@ -83,6 +83,7 @@ function createUPlotInstance(cursorIdx: number | null): uPlot {
|
||||
|
||||
function renderTooltip(props: Partial<TooltipTestProps> = {}): RenderResult {
|
||||
const defaultProps: TooltipTestProps = {
|
||||
id: 'tooltip-1',
|
||||
uPlotInstance: createUPlotInstance(null),
|
||||
timezone: { value: 'UTC', name: 'UTC', offset: '0', searchIndex: '0' },
|
||||
content: [],
|
||||
@@ -192,63 +193,88 @@ describe('Tooltip', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tooltip footer hint', () => {
|
||||
describe('Tooltip renderTooltipFooter', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseIsDarkMode.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it('renders footer with "Press P to pin the tooltip" hint when not pinned', () => {
|
||||
renderTooltip({ isPinned: false, canPinTooltip: true });
|
||||
it('does not render footer content when renderTooltipFooter is not provided', () => {
|
||||
renderTooltip();
|
||||
|
||||
const footer = screen.getByTestId('uplot-tooltip-footer');
|
||||
expect(footer).toBeInTheDocument();
|
||||
expect(footer).toHaveTextContent('Press');
|
||||
expect(footer).toHaveTextContent('P');
|
||||
expect(footer).toHaveTextContent('to pin the tooltip');
|
||||
expect(screen.queryByTestId('custom-tooltip-footer')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders footer with "Press P or Esc to unpin" hint when pinned', () => {
|
||||
renderTooltip({ isPinned: true, canPinTooltip: true });
|
||||
it('renders content returned by renderTooltipFooter', () => {
|
||||
const renderTooltipFooter = jest.fn(
|
||||
(): JSX.Element => <div data-testid="custom-tooltip-footer">Footer</div>,
|
||||
);
|
||||
|
||||
const footer = screen.getByTestId('uplot-tooltip-footer');
|
||||
expect(footer).toHaveTextContent('Press');
|
||||
expect(footer).toHaveTextContent('P');
|
||||
expect(footer).toHaveTextContent('Esc');
|
||||
expect(footer).toHaveTextContent('to unpin');
|
||||
renderTooltip({ renderTooltipFooter });
|
||||
|
||||
expect(screen.getByTestId('custom-tooltip-footer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render Unpin button when not pinned', () => {
|
||||
renderTooltip({ isPinned: false, canPinTooltip: true });
|
||||
it('calls renderTooltipFooter with isPinned=false when tooltip is not pinned', () => {
|
||||
const renderTooltipFooter = jest.fn(() => null);
|
||||
|
||||
expect(screen.queryByTestId('uplot-tooltip-unpin')).not.toBeInTheDocument();
|
||||
renderTooltip({ renderTooltipFooter, isPinned: false });
|
||||
|
||||
expect(renderTooltipFooter).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ isPinned: false }),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders Unpin button when pinned', () => {
|
||||
renderTooltip({ isPinned: true, canPinTooltip: true });
|
||||
it('calls renderTooltipFooter with isPinned=true when tooltip is pinned', () => {
|
||||
const renderTooltipFooter = jest.fn(() => null);
|
||||
|
||||
const unpinBtn = screen.getByTestId('uplot-tooltip-unpin');
|
||||
expect(unpinBtn).toBeInTheDocument();
|
||||
expect(unpinBtn).toHaveAttribute('aria-label', 'Unpin tooltip');
|
||||
renderTooltip({ renderTooltipFooter, isPinned: true });
|
||||
|
||||
expect(renderTooltipFooter).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ isPinned: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it('calls dismiss when Unpin button is clicked', async () => {
|
||||
it('calls renderTooltipFooter with the dismiss callback', () => {
|
||||
const dismiss = jest.fn();
|
||||
renderTooltip({ isPinned: true, canPinTooltip: true, dismiss });
|
||||
const renderTooltipFooter = jest.fn(() => null);
|
||||
|
||||
renderTooltip({ renderTooltipFooter, dismiss });
|
||||
|
||||
expect(renderTooltipFooter).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ dismiss }),
|
||||
);
|
||||
});
|
||||
|
||||
it('footer content reflects pinned state via renderTooltipFooter args', () => {
|
||||
const renderTooltipFooter = jest.fn(
|
||||
({ isPinned }: IRenderTooltipFooterArgs): JSX.Element => (
|
||||
<div data-testid="footer-state">{isPinned ? 'Pinned' : 'Not pinned'}</div>
|
||||
),
|
||||
);
|
||||
|
||||
renderTooltip({ renderTooltipFooter, isPinned: true });
|
||||
|
||||
expect(screen.getByTestId('footer-state')).toHaveTextContent('Pinned');
|
||||
});
|
||||
|
||||
it('dismiss is callable when invoked from renderTooltipFooter', async () => {
|
||||
const dismiss = jest.fn();
|
||||
const renderTooltipFooter = jest.fn(
|
||||
({ dismiss: onDismiss }: IRenderTooltipFooterArgs): JSX.Element => (
|
||||
<button data-testid="dismiss-btn" onClick={onDismiss}>
|
||||
Dismiss
|
||||
</button>
|
||||
),
|
||||
);
|
||||
|
||||
renderTooltip({ renderTooltipFooter, isPinned: true, dismiss });
|
||||
|
||||
const user = userEvent.setup();
|
||||
const unpinBtn = screen.getByTestId('uplot-tooltip-unpin');
|
||||
await user.click(unpinBtn);
|
||||
await user.click(screen.getByTestId('dismiss-btn'));
|
||||
|
||||
expect(dismiss).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('footer has role="status" for screen reader announcements', () => {
|
||||
renderTooltip({ canPinTooltip: true });
|
||||
|
||||
const footer = screen.getByRole('status');
|
||||
expect(footer).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tooltip header status pill', () => {
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-2);
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.pinnedItem {
|
||||
padding: var(--spacing-4) var(--spacing-4) 0 var(--spacing-4);
|
||||
padding: var(--spacing-4);
|
||||
}
|
||||
|
||||
.status {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
.container {
|
||||
padding-bottom: var(--spacing-6);
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 100%;
|
||||
:global(div[data-viewport-type='element']) {
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 0px var(--spacing-2) 0 var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-2) var(--spacing-4) var(--spacing-4);
|
||||
|
||||
[data-test-id='virtuoso-item-list'] > * + * {
|
||||
margin-top: var(--spacing-2);
|
||||
|
||||
@@ -9,17 +9,18 @@ import logEvent from 'api/common/logEvent';
|
||||
import { Events } from 'constants/events';
|
||||
|
||||
import Styles from './TooltipList.module.scss';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
// Fallback per-item height before Virtuoso reports the real total.
|
||||
const TOOLTIP_ITEM_HEIGHT = 38;
|
||||
const LIST_MAX_HEIGHT = 300;
|
||||
|
||||
interface TooltipListProps {
|
||||
id: string;
|
||||
content: TooltipContentItem[];
|
||||
}
|
||||
|
||||
export default function TooltipList({
|
||||
id,
|
||||
content,
|
||||
}: TooltipListProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -41,23 +42,25 @@ export default function TooltipList({
|
||||
if (!isScrollEventTriggered.current) {
|
||||
// TODO: remove event in July 2026
|
||||
logEvent(Events.TOOLTIP_CONTENT_SCROLLED, {
|
||||
path: getAbsoluteUrl(window.location.pathname),
|
||||
id,
|
||||
});
|
||||
isScrollEventTriggered.current = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Virtuoso
|
||||
className={cx(Styles.list, !isDarkMode && Styles.listLightMode)}
|
||||
data-testid="uplot-tooltip-list"
|
||||
data={content}
|
||||
onScroll={handleScroll}
|
||||
style={{ height }}
|
||||
totalListHeightChanged={setTotalListHeight}
|
||||
itemContent={(_, item): JSX.Element => (
|
||||
<TooltipItem item={item} isItemActive={false} />
|
||||
)}
|
||||
/>
|
||||
<div className={Styles.container}>
|
||||
<Virtuoso
|
||||
className={cx(Styles.list, !isDarkMode && Styles.listLightMode)}
|
||||
data-testid="uplot-tooltip-list"
|
||||
data={content}
|
||||
onScroll={handleScroll}
|
||||
style={{ height }}
|
||||
totalListHeightChanged={setTotalListHeight}
|
||||
itemContent={(_, item): JSX.Element => (
|
||||
<TooltipItem item={item} isItemActive={false} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,12 +60,20 @@ export interface TooltipRenderArgs {
|
||||
viaSync: boolean;
|
||||
}
|
||||
|
||||
export interface IRenderTooltipFooterArgs {
|
||||
pinKey?: string;
|
||||
isPinned: boolean;
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
export interface BaseTooltipProps {
|
||||
id: string;
|
||||
showTooltipHeader?: boolean;
|
||||
canPinTooltip?: boolean;
|
||||
yAxisUnit?: string;
|
||||
decimalPrecision?: PrecisionOption;
|
||||
content?: TooltipContentItem[];
|
||||
renderTooltipFooter?: (args: IRenderTooltipFooterArgs) => ReactNode;
|
||||
timezone?: Timezone;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
import { Events } from 'constants/events';
|
||||
|
||||
import Styles from './TooltipPlugin.module.scss';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
// Delay before hiding an unpinned tooltip when the cursor briefly leaves
|
||||
// the plot – this avoids flicker when moving between nearby points.
|
||||
@@ -300,7 +299,7 @@ export default function TooltipPlugin({
|
||||
if (event.key === 'Escape') {
|
||||
if (controller.pinned) {
|
||||
logEvent(Events.TOOLTIP_UNPINNED, {
|
||||
path: getAbsoluteUrl(window.location.pathname),
|
||||
id: config.getId(),
|
||||
});
|
||||
dismissTooltip();
|
||||
}
|
||||
@@ -314,7 +313,7 @@ export default function TooltipPlugin({
|
||||
// Toggle off: P pressed while already pinned.
|
||||
if (controller.pinned) {
|
||||
logEvent(Events.TOOLTIP_UNPINNED, {
|
||||
path: getAbsoluteUrl(window.location.pathname),
|
||||
id: config.getId(),
|
||||
});
|
||||
dismissTooltip();
|
||||
return;
|
||||
@@ -348,7 +347,7 @@ export default function TooltipPlugin({
|
||||
controller.clickData = buildClickData(syntheticEvent, plot);
|
||||
controller.pinned = true;
|
||||
logEvent(Events.TOOLTIP_PINNED, {
|
||||
path: getAbsoluteUrl(window.location.pathname),
|
||||
id: config.getId(),
|
||||
});
|
||||
scheduleRender(true);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user