mirror of https://github.com/grafana/grafana.git
Re-enable `jsx-a11y` recommended rules (#104637)
* Re-enable `jsx-a11y` recommended rules * apply rule in correct place, couple of fixes * fix up some a11y issues * add ignore for keyboard a11y for now * readd testid * close carousel on backdrop click * use type="button" --------- Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
parent
60ea65ca69
commit
6f3200d4f0
|
@ -173,9 +173,7 @@ module.exports = [
|
||||||
files: ['**/*.tsx'],
|
files: ['**/*.tsx'],
|
||||||
ignores: ['**/*.{spec,test}.tsx'],
|
ignores: ['**/*.{spec,test}.tsx'],
|
||||||
rules: {
|
rules: {
|
||||||
// rules marked "off" are those left in the recommended preset we need to fix
|
...jsxA11yPlugin.configs.recommended.rules,
|
||||||
// we should remove the corresponding line and fix them one by one
|
|
||||||
// any marked "error" contain specific overrides we'll need to keep
|
|
||||||
'jsx-a11y/no-autofocus': [
|
'jsx-a11y/no-autofocus': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { useState, useEffect } from 'react';
|
import { useDialog } from '@react-aria/dialog';
|
||||||
|
import { FocusScope } from '@react-aria/focus';
|
||||||
|
import { OverlayContainer, useOverlay } from '@react-aria/overlays';
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
import { useStyles2 } from '../../themes';
|
import { useStyles2 } from '../../themes';
|
||||||
import { t } from '../../utils/i18n';
|
import { t } from '../../utils/i18n';
|
||||||
import { Alert } from '../Alert/Alert';
|
import { Alert } from '../Alert/Alert';
|
||||||
|
import { clearButtonStyles } from '../Button';
|
||||||
import { IconButton } from '../IconButton/IconButton';
|
import { IconButton } from '../IconButton/IconButton';
|
||||||
|
|
||||||
// Define the image item interface
|
// Define the image item interface
|
||||||
|
@ -23,7 +27,8 @@ export const Carousel: React.FC<CarouselProps> = ({ images }) => {
|
||||||
const [imageErrors, setImageErrors] = useState<Record<string, boolean>>({});
|
const [imageErrors, setImageErrors] = useState<Record<string, boolean>>({});
|
||||||
const [validImages, setValidImages] = useState<CarouselImage[]>(images);
|
const [validImages, setValidImages] = useState<CarouselImage[]>(images);
|
||||||
|
|
||||||
const styles = useStyles2(getStyles());
|
const styles = useStyles2(getStyles);
|
||||||
|
const resetButtonStyles = useStyles2(clearButtonStyles);
|
||||||
|
|
||||||
const handleImageError = (path: string) => {
|
const handleImageError = (path: string) => {
|
||||||
setImageErrors((prev) => ({
|
setImageErrors((prev) => ({
|
||||||
|
@ -77,6 +82,11 @@ export const Carousel: React.FC<CarouselProps> = ({ images }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { overlayProps, underlayProps } = useOverlay({ isOpen: selectedIndex !== null, onClose: closePreview }, ref);
|
||||||
|
const { dialogProps } = useDialog({}, ref);
|
||||||
|
|
||||||
if (validImages.length === 0) {
|
if (validImages.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
|
@ -88,72 +98,88 @@ export const Carousel: React.FC<CarouselProps> = ({ images }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onKeyDown={handleKeyDown} tabIndex={0}>
|
<>
|
||||||
<div className={cx(styles.imageGrid)}>
|
<div className={cx(styles.imageGrid)}>
|
||||||
{validImages.map((image, index) => (
|
{validImages.map((image, index) => (
|
||||||
<div key={image.path} onClick={() => openPreview(index)} style={{ cursor: 'pointer' }}>
|
<button
|
||||||
|
type="button"
|
||||||
|
key={image.path}
|
||||||
|
onClick={() => openPreview(index)}
|
||||||
|
className={cx(resetButtonStyles, styles.imageButton)}
|
||||||
|
>
|
||||||
<img src={image.path} alt={image.name} onError={() => handleImageError(image.path)} />
|
<img src={image.path} alt={image.name} onError={() => handleImageError(image.path)} />
|
||||||
<p>{image.name}</p>
|
<p>{image.name}</p>
|
||||||
</div>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedIndex !== null && (
|
{selectedIndex !== null && (
|
||||||
<div className={cx(styles.fullScreenDiv)} onClick={closePreview} data-testid="carousel-full-screen">
|
<OverlayContainer>
|
||||||
<IconButton
|
<div role="presentation" className={styles.underlay} onClick={closePreview} {...underlayProps} />
|
||||||
name="times"
|
<FocusScope contain autoFocus restoreFocus>
|
||||||
aria-label={t('carousel.close', 'Close')}
|
{/* convenience method for keyboard users */}
|
||||||
size="xl"
|
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||||
onClick={closePreview}
|
<div
|
||||||
className={cx(styles.closeButton)}
|
data-testid="carousel-full-screen"
|
||||||
/>
|
ref={ref}
|
||||||
|
{...overlayProps}
|
||||||
|
{...dialogProps}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
className={styles.overlay}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
name="times"
|
||||||
|
aria-label={t('carousel.close', 'Close')}
|
||||||
|
size="xl"
|
||||||
|
onClick={closePreview}
|
||||||
|
className={cx(styles.closeButton)}
|
||||||
|
/>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="xl"
|
size="xl"
|
||||||
name="angle-left"
|
name="angle-left"
|
||||||
aria-label={t('carousel.previous', 'Previous')}
|
aria-label={t('carousel.previous', 'Previous')}
|
||||||
onClick={(e) => {
|
onClick={goToPrevious}
|
||||||
e.stopPropagation();
|
data-testid="previous-button"
|
||||||
goToPrevious();
|
/>
|
||||||
}}
|
|
||||||
className={cx(styles.navigationButton, styles.previousButton)}
|
|
||||||
data-testid="previous-button"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div data-testid="carousel-full-image">
|
||||||
style={{ position: 'relative', maxWidth: '90%', maxHeight: '90%' }}
|
<img
|
||||||
onClick={(e) => e.stopPropagation()}
|
className={styles.imagePreview}
|
||||||
data-testid="carousel-full-image"
|
src={validImages[selectedIndex].path}
|
||||||
>
|
alt={validImages[selectedIndex].name}
|
||||||
<img
|
onError={() => handleImageError(validImages[selectedIndex].path)}
|
||||||
src={validImages[selectedIndex].path}
|
/>
|
||||||
alt={validImages[selectedIndex].name}
|
</div>
|
||||||
onError={() => handleImageError(validImages[selectedIndex].path)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="xl"
|
size="xl"
|
||||||
name="angle-right"
|
name="angle-right"
|
||||||
aria-label={t('carousel.next', 'Next')}
|
aria-label={t('carousel.next', 'Next')}
|
||||||
onClick={(e) => {
|
onClick={goToNext}
|
||||||
e.stopPropagation();
|
data-testid="next-button"
|
||||||
goToNext();
|
/>
|
||||||
}}
|
</div>
|
||||||
className={cx(styles.navigationButton, styles.nextButton)}
|
</FocusScope>
|
||||||
data-testid="next-button"
|
</OverlayContainer>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = () => (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
imageButton: css({
|
||||||
|
textAlign: 'left',
|
||||||
|
}),
|
||||||
|
imagePreview: css({
|
||||||
|
maxWidth: '100%',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
objectFit: 'contain',
|
||||||
|
}),
|
||||||
imageGrid: css({
|
imageGrid: css({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: `repeat(auto-fill, minmax(200px, 1fr))`,
|
gridTemplateColumns: `repeat(auto-fill, minmax(200px, 1fr))`,
|
||||||
gap: '16px',
|
gap: theme.spacing(2),
|
||||||
marginBottom: '20px',
|
marginBottom: '20px',
|
||||||
|
|
||||||
'& img': {
|
'& img': {
|
||||||
|
@ -165,49 +191,33 @@ const getStyles = () => (theme: GrafanaTheme2) => ({
|
||||||
boxShadow: theme.shadows.z1,
|
boxShadow: theme.shadows.z1,
|
||||||
},
|
},
|
||||||
'& p': {
|
'& p': {
|
||||||
margin: '4px 0',
|
margin: theme.spacing(0.5, 0),
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
color: theme.colors.text.primary,
|
color: theme.colors.text.primary,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
fullScreenDiv: css({
|
underlay: css({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
zIndex: theme.zIndex.modalBackdrop,
|
zIndex: theme.zIndex.modalBackdrop,
|
||||||
top: 0,
|
inset: 0,
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
backgroundColor: theme.components.overlay.background,
|
backgroundColor: theme.components.overlay.background,
|
||||||
|
}),
|
||||||
|
overlay: css({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
'& img': {
|
height: 'fit-content',
|
||||||
maxWidth: '100%',
|
marginBottom: 'auto',
|
||||||
maxHeight: '80vh',
|
marginTop: 'auto',
|
||||||
objectFit: 'contain',
|
padding: theme.spacing(2),
|
||||||
},
|
position: 'fixed',
|
||||||
|
inset: 0,
|
||||||
|
zIndex: theme.zIndex.modal,
|
||||||
}),
|
}),
|
||||||
closeButton: css({
|
closeButton: css({
|
||||||
position: 'absolute',
|
|
||||||
top: '20px',
|
|
||||||
right: '20px',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
color: theme.colors.text.primary,
|
color: theme.colors.text.primary,
|
||||||
}),
|
position: 'fixed',
|
||||||
navigationButton: css({
|
top: theme.spacing(2),
|
||||||
position: 'absolute',
|
right: theme.spacing(2),
|
||||||
backgroundColor: 'transparent',
|
|
||||||
color: theme.colors.text.primary,
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}),
|
|
||||||
nextButton: css({
|
|
||||||
right: '20px',
|
|
||||||
}),
|
|
||||||
previousButton: css({
|
|
||||||
left: '20px',
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -99,6 +99,7 @@ const HeaderCell: React.FC<HeaderCellProps> = ({
|
||||||
}, [column]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [column]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
<div
|
<div
|
||||||
ref={headerRef}
|
ref={headerRef}
|
||||||
className={styles.headerCell}
|
className={styles.headerCell}
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function RowExpander({ height, onCellExpand, isExpanded }: RowExpanderNGP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles.expanderCell} onClick={onCellExpand} onKeyDown={handleKeyDown}>
|
<div role="button" tabIndex={0} className={styles.expanderCell} onClick={onCellExpand} onKeyDown={handleKeyDown}>
|
||||||
<Icon
|
<Icon
|
||||||
aria-label={
|
aria-label={
|
||||||
isExpanded
|
isExpanded
|
||||||
|
|
|
@ -75,6 +75,8 @@ function ThemeCard({ themeOption, isExperimental, isSelected, onSelect }: ThemeC
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// this is a convenience for mouse users. keyboard/screen reader users will use the radio button
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
|
||||||
<div className={styles.card} onClick={onSelect}>
|
<div className={styles.card} onClick={onSelect}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<RadioButtonDot
|
<RadioButtonDot
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
VizPanel,
|
VizPanel,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import {
|
import {
|
||||||
|
clearButtonStyles,
|
||||||
ElementSelectionContextItem,
|
ElementSelectionContextItem,
|
||||||
ElementSelectionContextState,
|
ElementSelectionContextState,
|
||||||
ElementSelectionOnSelectOptions,
|
ElementSelectionOnSelectOptions,
|
||||||
|
@ -199,6 +200,7 @@ export interface Props {
|
||||||
export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleCollapse, openOverlay }: Props) {
|
export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleCollapse, openOverlay }: Props) {
|
||||||
const { selection } = useSceneObjectState(editPane, { shouldActivateOrKeepAlive: true });
|
const { selection } = useSceneObjectState(editPane, { shouldActivateOrKeepAlive: true });
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const clearButton = useStyles2(clearButtonStyles);
|
||||||
const editableElement = useEditableElement(selection, editPane);
|
const editableElement = useEditableElement(selection, editPane);
|
||||||
const selectedObject = selection?.getFirstObject();
|
const selectedObject = selection?.getFirstObject();
|
||||||
const isNewElement = selection?.isNewElement() ?? false;
|
const isNewElement = selection?.isNewElement() ?? false;
|
||||||
|
@ -280,17 +282,17 @@ export function DashboardEditPaneRenderer({ editPane, isCollapsed, onToggleColla
|
||||||
data-edit-pane-splitter={true}
|
data-edit-pane-splitter={true}
|
||||||
/>
|
/>
|
||||||
<div {...splitter.secondaryProps} className={cx(splitter.primaryProps.className, styles.paneContent)}>
|
<div {...splitter.secondaryProps} className={cx(splitter.primaryProps.className, styles.paneContent)}>
|
||||||
<div
|
<button
|
||||||
role="button"
|
type="button"
|
||||||
onClick={() => setOutlineCollapsed(!outlineCollapsed)}
|
onClick={() => setOutlineCollapsed(!outlineCollapsed)}
|
||||||
className={styles.outlineCollapseButton}
|
className={cx(clearButton, styles.outlineCollapseButton)}
|
||||||
data-testid={selectors.components.PanelEditor.Outline.section}
|
data-testid={selectors.components.PanelEditor.Outline.section}
|
||||||
>
|
>
|
||||||
<Text weight="medium">
|
<Text weight="medium">
|
||||||
<Trans i18nKey="dashboard-scene.dashboard-edit-pane-renderer.outline">Outline</Trans>
|
<Trans i18nKey="dashboard-scene.dashboard-edit-pane-renderer.outline">Outline</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Icon name={outlineCollapsed ? 'angle-up' : 'angle-down'} />
|
<Icon name={outlineCollapsed ? 'angle-up' : 'angle-down'} />
|
||||||
</div>
|
</button>
|
||||||
{!outlineCollapsed && (
|
{!outlineCollapsed && (
|
||||||
<div className={styles.outlineContainer}>
|
<div className={styles.outlineContainer}>
|
||||||
<ScrollContainer showScrollIndicators={true}>
|
<ScrollContainer showScrollIndicators={true}>
|
||||||
|
|
|
@ -90,6 +90,8 @@ function DashboardOutlineNode({
|
||||||
>
|
>
|
||||||
{elementInfo.isContainer && (
|
{elementInfo.isContainer && (
|
||||||
<button
|
<button
|
||||||
|
// TODO fix keyboard a11y here
|
||||||
|
// eslint-disable-next-line jsx-a11y/role-has-required-aria-props
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
className={styles.angleButton}
|
className={styles.angleButton}
|
||||||
onPointerDown={onToggleCollapse}
|
onPointerDown={onToggleCollapse}
|
||||||
|
@ -99,7 +101,6 @@ function DashboardOutlineNode({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
role="button"
|
|
||||||
className={cx(styles.nodeName, isCloned && styles.nodeNameClone)}
|
className={cx(styles.nodeName, isCloned && styles.nodeNameClone)}
|
||||||
onDoubleClick={outlineRename.onNameDoubleClicked}
|
onDoubleClick={outlineRename.onNameDoubleClicked}
|
||||||
data-testid={selectors.components.PanelEditor.Outline.item(instanceName)}
|
data-testid={selectors.components.PanelEditor.Outline.item(instanceName)}
|
||||||
|
|
|
@ -112,7 +112,6 @@ export function RowItemRenderer({ model }: SceneComponentProps<RowItem>) {
|
||||||
!isTopLevel && styles.rowTitleNested,
|
!isTopLevel && styles.rowTitleNested,
|
||||||
isCollapsed && styles.rowTitleCollapsed
|
isCollapsed && styles.rowTitleCollapsed
|
||||||
)}
|
)}
|
||||||
role="heading"
|
|
||||||
>
|
>
|
||||||
{!model.hasUniqueTitle() && (
|
{!model.hasUniqueTitle() && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|
|
@ -65,7 +65,6 @@ export function TabItemRenderer({ model }: SceneComponentProps<TabItem>) {
|
||||||
isDropTarget && 'dashboard-drop-target'
|
isDropTarget && 'dashboard-drop-target'
|
||||||
)}
|
)}
|
||||||
active={isActive}
|
active={isActive}
|
||||||
role="presentation"
|
|
||||||
title={titleInterpolated}
|
title={titleInterpolated}
|
||||||
href={href}
|
href={href}
|
||||||
aria-selected={isActive}
|
aria-selected={isActive}
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
||||||
checkedLabel?: string;
|
checkedLabel?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
'data-testid'?: string;
|
'data-testid'?: string;
|
||||||
onClick: (evt: MouseEvent<HTMLDivElement>) => void;
|
onClick: (evt: MouseEvent<HTMLButtonElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolbarSwitch = ({
|
export const ToolbarSwitch = ({
|
||||||
|
@ -32,9 +32,8 @@ export const ToolbarSwitch = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={labelText}>
|
<Tooltip content={labelText}>
|
||||||
<div
|
<button
|
||||||
aria-label={labelText}
|
aria-label={labelText}
|
||||||
role="button"
|
|
||||||
className={cx({
|
className={cx({
|
||||||
[styles.container]: true,
|
[styles.container]: true,
|
||||||
[styles.containerChecked]: checked,
|
[styles.containerChecked]: checked,
|
||||||
|
@ -46,7 +45,7 @@ export const ToolbarSwitch = ({
|
||||||
<div className={cx(styles.box, checked && styles.boxChecked)}>
|
<div className={cx(styles.box, checked && styles.boxChecked)}>
|
||||||
<Icon name={iconName} size="xs" />
|
<Icon name={iconName} size="xs" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -76,6 +76,8 @@ function VariableList({ set }: { set: SceneVariableSet }) {
|
||||||
return (
|
return (
|
||||||
<Stack direction="column" gap={0}>
|
<Stack direction="column" gap={0}>
|
||||||
{variables.map((variable) => (
|
{variables.map((variable) => (
|
||||||
|
// TODO fix keyboard a11y here
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
|
||||||
<div className={styles.variableItem} key={variable.state.name} onClick={() => onEditVariable(variable)}>
|
<div className={styles.variableItem} key={variable.state.name} onClick={() => onEditVariable(variable)}>
|
||||||
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
|
||||||
<Text>${variable.state.name}</Text>
|
<Text>${variable.state.name}</Text>
|
||||||
|
|
|
@ -88,15 +88,16 @@ const Ellipsis = ({ toggle, diff }: EllipsisProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Trans i18nKey="logs.log-row-message.ellipsis">… </Trans>
|
<Trans i18nKey="logs.log-row-message.ellipsis">… </Trans>
|
||||||
<span className={styles.showMore} onClick={handleClick}>
|
<button className={styles.showMore} onClick={handleClick}>
|
||||||
{diff} <Trans i18nKey="logs.log-row-message.more">more</Trans>
|
{diff} <Trans i18nKey="logs.log-row-message.more">more</Trans>
|
||||||
</span>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEllipsisStyles = (theme: GrafanaTheme2) => ({
|
const getEllipsisStyles = (theme: GrafanaTheme2) => ({
|
||||||
showMore: css({
|
showMore: css({
|
||||||
|
backgroundColor: 'transparent',
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
fontSize: theme.typography.size.sm,
|
fontSize: theme.typography.size.sm,
|
||||||
|
|
|
@ -76,6 +76,7 @@ export const LogLine = ({
|
||||||
className={`${styles.logLine} ${variant ?? ''} ${pinned ? styles.pinnedLogLine : ''}`}
|
className={`${styles.logLine} ${variant ?? ''} ${pinned ? styles.pinnedLogLine : ''}`}
|
||||||
ref={onOverflow ? logLineRef : undefined}
|
ref={onOverflow ? logLineRef : undefined}
|
||||||
onMouseEnter={handleMouseOver}
|
onMouseEnter={handleMouseOver}
|
||||||
|
onFocus={handleMouseOver}
|
||||||
>
|
>
|
||||||
<LogLineMenu styles={styles} log={log} />
|
<LogLineMenu styles={styles} log={log} />
|
||||||
<div
|
<div
|
||||||
|
|
Loading…
Reference in New Issue