mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:55:59 +08:00
feat: handle keyboard navigations for column selection in logs explor… (#6548)
* feat: handle keyboard navigations for column selection in logs explorer options view * chore: remove console log
This commit is contained in:
parent
2bfd31841e
commit
6aee991633
@ -3,7 +3,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: -2px;
|
right: -2px;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
width: 160px;
|
width: 240px;
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@
|
|||||||
.back-btn {
|
.back-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@ -32,14 +32,16 @@
|
|||||||
.icon {
|
.icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: var(--bg-vanilla-400);
|
color: var(--bg-slate-50);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 13px;
|
font-size: 11px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 500;
|
||||||
line-height: 20px; /* 142.857% */
|
line-height: 20px; /* 142.857% */
|
||||||
letter-spacing: 0.14px;
|
letter-spacing: 0.14px;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +254,75 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-new-column-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.add-new-column-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.88px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new-column-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
min-height: 240px;
|
||||||
|
max-height: 400px;
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-format-new-options {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.column-name {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 1px;
|
||||||
|
color: var(--bg-vanilla-400, #c0c1c3);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: var(--bg-ink-200);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 1rem;
|
||||||
|
width: 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.selected-item-content-container {
|
.selected-item-content-container {
|
||||||
.add-new-column-header {
|
.add-new-column-header {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@ -314,6 +385,22 @@
|
|||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.default-column {
|
||||||
|
color: var(--bg-vanilla-400, #c0c1c3);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-columns-selected {
|
||||||
|
color: var(--bg-slate-100);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.add-new-column-btn {
|
||||||
|
color: var(--bg-vanilla-400, #c0c1c3);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -428,6 +515,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-new-column-container {
|
||||||
|
.add-new-column-header {
|
||||||
|
.title {
|
||||||
|
color: var(--bg-ink-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new-column-content {
|
||||||
|
.column-format-new-options {
|
||||||
|
.column-name {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: var(--bg-vanilla-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.font-size-container {
|
.font-size-container {
|
||||||
.title {
|
.title {
|
||||||
color: var(--bg-ink-100);
|
color: var(--bg-ink-100);
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
import './LogsFormatOptionsMenu.styles.scss';
|
import './LogsFormatOptionsMenu.styles.scss';
|
||||||
|
|
||||||
import { Button, Divider, Input, InputNumber, Tooltip, Typography } from 'antd';
|
import { Button, Input, InputNumber, Tooltip, Typography } from 'antd';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { LogViewMode } from 'container/LogsTable';
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { Check, ChevronLeft, ChevronRight, Minus, Plus, X } from 'lucide-react';
|
import { Check, ChevronLeft, ChevronRight, Minus, Plus, X } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface LogsFormatOptionsMenuProps {
|
interface LogsFormatOptionsMenuProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -35,7 +35,13 @@ export default function LogsFormatOptionsMenu({
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [addNewColumn, setAddNewColumn] = useState(false);
|
const [showAddNewColumnContainer, setShowAddNewColumnContainer] = useState(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
||||||
|
const listRef = useRef<HTMLDivElement>(null);
|
||||||
|
const initialMouseEnterRef = useRef<boolean>(false);
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(key: LogViewMode) => {
|
(key: LogViewMode) => {
|
||||||
@ -49,7 +55,7 @@ export default function LogsFormatOptionsMenu({
|
|||||||
const handleMenuItemClick = (key: LogViewMode): void => {
|
const handleMenuItemClick = (key: LogViewMode): void => {
|
||||||
setSelectedItem(key);
|
setSelectedItem(key);
|
||||||
onChange(key);
|
onChange(key);
|
||||||
setAddNewColumn(false);
|
setShowAddNewColumnContainer(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const incrementMaxLinesPerRow = (): void => {
|
const incrementMaxLinesPerRow = (): void => {
|
||||||
@ -75,7 +81,7 @@ export default function LogsFormatOptionsMenu({
|
|||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const handleToggleAddNewColumn = (): void => {
|
const handleToggleAddNewColumn = (): void => {
|
||||||
setAddNewColumn(!addNewColumn);
|
setShowAddNewColumnContainer(!showAddNewColumnContainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
|
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
|
||||||
@ -100,9 +106,92 @@ export default function LogsFormatOptionsMenu({
|
|||||||
}
|
}
|
||||||
}, [fontSizeValue]);
|
}, [fontSizeValue]);
|
||||||
|
|
||||||
|
const handleKeyDown = (e: KeyboardEvent): void => {
|
||||||
|
if (!selectedValue) return;
|
||||||
|
|
||||||
|
const optionsData = addColumn?.options || [];
|
||||||
|
|
||||||
|
const currentIndex = optionsData.findIndex(
|
||||||
|
(item) => item?.value === selectedValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentItem = optionsData[currentIndex];
|
||||||
|
|
||||||
|
const itemLength = optionsData.length;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowUp': {
|
||||||
|
const newValue = optionsData[Math.max(0, currentIndex - 1)]?.value;
|
||||||
|
|
||||||
|
setSelectedValue(newValue as string | null);
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ArrowDown': {
|
||||||
|
const newValue =
|
||||||
|
optionsData[Math.min(itemLength - 1, currentIndex + 1)]?.value;
|
||||||
|
|
||||||
|
setSelectedValue(newValue as string | null);
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'Enter':
|
||||||
|
e.preventDefault();
|
||||||
|
if (addColumn && addColumn?.onSelect) {
|
||||||
|
addColumn?.onSelect(selectedValue, {
|
||||||
|
label: currentItem.label,
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentIndex === itemLength - 1) {
|
||||||
|
setSelectedValue(null);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
const nextIndex = currentIndex + 1;
|
||||||
|
|
||||||
|
const nextValue = optionsData[nextIndex]?.value || null;
|
||||||
|
|
||||||
|
setSelectedValue(nextValue as string | null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Scroll the selected item into view
|
||||||
|
const listNode = listRef.current;
|
||||||
|
if (listNode && selectedValue) {
|
||||||
|
const optionsData = addColumn?.options || [];
|
||||||
|
const currentIndex = optionsData.findIndex(
|
||||||
|
(item) => item?.value === selectedValue,
|
||||||
|
);
|
||||||
|
const itemNode = listNode.children[currentIndex] as HTMLElement;
|
||||||
|
if (itemNode) {
|
||||||
|
itemNode.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return (): void => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [selectedValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx('nested-menu-container', addNewColumn ? 'active' : '')}
|
className={cx(
|
||||||
|
'nested-menu-container',
|
||||||
|
showAddNewColumnContainer ? 'active' : '',
|
||||||
|
)}
|
||||||
onClick={(event): void => {
|
onClick={(event): void => {
|
||||||
// this is to restrict click events to propogate to parent
|
// this is to restrict click events to propogate to parent
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -158,8 +247,73 @@ export default function LogsFormatOptionsMenu({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : null}
|
||||||
<>
|
|
||||||
|
{showAddNewColumnContainer && (
|
||||||
|
<div className="add-new-column-container">
|
||||||
|
<div className="add-new-column-header">
|
||||||
|
<div className="title">
|
||||||
|
<div className="periscope-btn ghost" onClick={handleToggleAddNewColumn}>
|
||||||
|
<ChevronLeft
|
||||||
|
size={14}
|
||||||
|
className="back-icon"
|
||||||
|
onClick={handleToggleAddNewColumn}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
Add New Column
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
tabIndex={0}
|
||||||
|
type="text"
|
||||||
|
autoFocus
|
||||||
|
onFocus={addColumn?.onFocus}
|
||||||
|
onChange={handleSearchValueChange}
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="add-new-column-content">
|
||||||
|
{addColumn?.isFetching && (
|
||||||
|
<div className="loading-container"> Loading ... </div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="column-format-new-options" ref={listRef}>
|
||||||
|
{addColumn?.options?.map(({ label, value }) => (
|
||||||
|
<div
|
||||||
|
className={cx('column-name', value === selectedValue && 'selected')}
|
||||||
|
key={value}
|
||||||
|
onMouseEnter={(): void => {
|
||||||
|
if (!initialMouseEnterRef.current) {
|
||||||
|
setSelectedValue(value as string | null);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialMouseEnterRef.current = true;
|
||||||
|
}}
|
||||||
|
onClick={(eve): void => {
|
||||||
|
eve.stopPropagation();
|
||||||
|
|
||||||
|
if (addColumn && addColumn?.onSelect) {
|
||||||
|
addColumn?.onSelect(value, { label, disabled: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedValue(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="name">
|
||||||
|
<Tooltip placement="left" title={label}>
|
||||||
|
{label}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isFontSizeOptionsOpen && !showAddNewColumnContainer && (
|
||||||
|
<div>
|
||||||
<div className="font-size-container">
|
<div className="font-size-container">
|
||||||
<div className="title">Font Size</div>
|
<div className="title">Font Size</div>
|
||||||
<Button
|
<Button
|
||||||
@ -230,29 +384,10 @@ export default function LogsFormatOptionsMenu({
|
|||||||
</>
|
</>
|
||||||
|
|
||||||
<div className="selected-item-content-container active">
|
<div className="selected-item-content-container active">
|
||||||
{!addNewColumn && <div className="horizontal-line" />}
|
{!showAddNewColumnContainer && <div className="horizontal-line" />}
|
||||||
|
|
||||||
{addNewColumn && (
|
|
||||||
<div className="add-new-column-header">
|
|
||||||
<div className="title">
|
|
||||||
{' '}
|
|
||||||
columns
|
|
||||||
<X size={14} onClick={handleToggleAddNewColumn} />{' '}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
tabIndex={0}
|
|
||||||
type="text"
|
|
||||||
autoFocus
|
|
||||||
onFocus={addColumn?.onFocus}
|
|
||||||
onChange={handleSearchValueChange}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="item-content">
|
<div className="item-content">
|
||||||
{!addNewColumn && (
|
{!showAddNewColumnContainer && (
|
||||||
<div className="title">
|
<div className="title">
|
||||||
columns
|
columns
|
||||||
<Plus size={14} onClick={handleToggleAddNewColumn} />{' '}
|
<Plus size={14} onClick={handleToggleAddNewColumn} />{' '}
|
||||||
@ -274,48 +409,17 @@ export default function LogsFormatOptionsMenu({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
{addColumn && addColumn?.value?.length === 0 && (
|
||||||
|
<div className="column-name no-columns-selected">
|
||||||
{addColumn?.isFetching && (
|
No columns selected
|
||||||
<div className="loading-container"> Loading ... </div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{addNewColumn &&
|
|
||||||
addColumn &&
|
|
||||||
addColumn.value.length > 0 &&
|
|
||||||
addColumn.options &&
|
|
||||||
addColumn?.options?.length > 0 && (
|
|
||||||
<Divider className="column-divider" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{addNewColumn && (
|
|
||||||
<div className="column-format-new-options">
|
|
||||||
{addColumn?.options?.map(({ label, value }) => (
|
|
||||||
<div
|
|
||||||
className="column-name"
|
|
||||||
key={value}
|
|
||||||
onClick={(eve): void => {
|
|
||||||
eve.stopPropagation();
|
|
||||||
|
|
||||||
if (addColumn && addColumn?.onSelect) {
|
|
||||||
addColumn?.onSelect(value, { label, disabled: false });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="name">
|
|
||||||
<Tooltip placement="left" title={label}>
|
|
||||||
{label}
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user