mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 21:39:05 +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;
|
||||
right: -2px;
|
||||
margin: 6px 0;
|
||||
width: 160px;
|
||||
width: 240px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
@ -32,14 +32,16 @@
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--bg-vanilla-400);
|
||||
color: var(--bg-slate-50);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
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 {
|
||||
.add-new-column-header {
|
||||
padding: 8px;
|
||||
@ -314,6 +385,22 @@
|
||||
|
||||
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 {
|
||||
flex: 1;
|
||||
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 {
|
||||
.title {
|
||||
color: var(--bg-ink-100);
|
||||
|
@ -3,13 +3,13 @@
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
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 { LogViewMode } from 'container/LogsTable';
|
||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
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 {
|
||||
title: string;
|
||||
@ -35,7 +35,13 @@ export default function LogsFormatOptionsMenu({
|
||||
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(
|
||||
(key: LogViewMode) => {
|
||||
@ -49,7 +55,7 @@ export default function LogsFormatOptionsMenu({
|
||||
const handleMenuItemClick = (key: LogViewMode): void => {
|
||||
setSelectedItem(key);
|
||||
onChange(key);
|
||||
setAddNewColumn(false);
|
||||
setShowAddNewColumnContainer(false);
|
||||
};
|
||||
|
||||
const incrementMaxLinesPerRow = (): void => {
|
||||
@ -75,7 +81,7 @@ export default function LogsFormatOptionsMenu({
|
||||
}, 300);
|
||||
|
||||
const handleToggleAddNewColumn = (): void => {
|
||||
setAddNewColumn(!addNewColumn);
|
||||
setShowAddNewColumnContainer(!showAddNewColumnContainer);
|
||||
};
|
||||
|
||||
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
|
||||
@ -100,9 +106,92 @@ export default function LogsFormatOptionsMenu({
|
||||
}
|
||||
}, [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 (
|
||||
<div
|
||||
className={cx('nested-menu-container', addNewColumn ? 'active' : '')}
|
||||
className={cx(
|
||||
'nested-menu-container',
|
||||
showAddNewColumnContainer ? 'active' : '',
|
||||
)}
|
||||
onClick={(event): void => {
|
||||
// this is to restrict click events to propogate to parent
|
||||
event.stopPropagation();
|
||||
@ -158,8 +247,73 @@ export default function LogsFormatOptionsMenu({
|
||||
</Button>
|
||||
</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="title">Font Size</div>
|
||||
<Button
|
||||
@ -230,29 +384,10 @@ export default function LogsFormatOptionsMenu({
|
||||
</>
|
||||
|
||||
<div className="selected-item-content-container active">
|
||||
{!addNewColumn && <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>
|
||||
)}
|
||||
{!showAddNewColumnContainer && <div className="horizontal-line" />}
|
||||
|
||||
<div className="item-content">
|
||||
{!addNewColumn && (
|
||||
{!showAddNewColumnContainer && (
|
||||
<div className="title">
|
||||
columns
|
||||
<Plus size={14} onClick={handleToggleAddNewColumn} />{' '}
|
||||
@ -274,48 +409,17 @@ export default function LogsFormatOptionsMenu({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{addColumn?.isFetching && (
|
||||
<div className="loading-container"> Loading ... </div>
|
||||
)}
|
||||
|
||||
{addNewColumn &&
|
||||
addColumn &&
|
||||
addColumn.value.length > 0 &&
|
||||
addColumn.options &&
|
||||
addColumn?.options?.length > 0 && (
|
||||
<Divider className="column-divider" />
|
||||
{addColumn && addColumn?.value?.length === 0 && (
|
||||
<div className="column-name no-columns-selected">
|
||||
No columns selected
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user