Explore: Add active state to ContentOutlineItemButton (#78779)

* Add active state to ContentOutlineItemButton

* Improve

* WIP: improve

* Cleanup

* Fix

* Improve betterer, remove extra curly braces
This commit is contained in:
Haris Rozajac 2024-01-04 08:15:50 -07:00 committed by GitHub
parent 40583aec0f
commit 20096259d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 17 deletions

View File

@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import React from 'react';
import { useToggle } from 'react-use';
import React, { useEffect, useRef, useState } from 'react';
import { useToggle, useScroll } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
@ -33,9 +33,12 @@ const getStyles = (theme: GrafanaTheme2) => {
};
export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement | undefined; panelId: string }) {
const [expanded, toggleExpanded] = useToggle(false);
const styles = useStyles2((theme) => getStyles(theme));
const { outlineItems } = useContentOutlineContext();
const [expanded, toggleExpanded] = useToggle(false);
const [activeItemId, setActiveItemId] = useState<string | undefined>(outlineItems[0]?.id);
const styles = useStyles2((theme) => getStyles(theme));
const scrollerRef = useRef(scroller || null);
const { y: verticalScroll } = useScroll(scrollerRef);
const scrollIntoView = (ref: HTMLElement | null, buttonTitle: string) => {
let scrollValue = 0;
@ -50,6 +53,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
top: scrollValue,
behavior: 'smooth',
});
reportInteraction('explore_toolbar_contentoutline_clicked', {
item: 'select_section',
type: buttonTitle,
@ -64,6 +68,24 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
});
};
useEffect(() => {
const activeItem = outlineItems.find((item) => {
const top = item?.ref?.getBoundingClientRect().top;
if (!top) {
return false;
}
return top >= 0;
});
if (!activeItem) {
return;
}
setActiveItemId(activeItem.id);
}, [outlineItems, verticalScroll]);
return (
<PanelContainer className={styles.wrapper} id={panelId}>
<CustomScrollbar>
@ -77,16 +99,19 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
aria-expanded={expanded}
/>
{outlineItems.map((item) => (
<ContentOutlineItemButton
key={item.id}
title={expanded ? item.title : undefined}
className={styles.buttonStyles}
icon={item.icon}
onClick={() => scrollIntoView(item.ref, item.title)}
tooltip={!expanded ? item.title : undefined}
/>
))}
{outlineItems.map((item) => {
return (
<ContentOutlineItemButton
key={item.id}
title={expanded ? item.title : undefined}
className={styles.buttonStyles}
icon={item.icon}
onClick={() => scrollIntoView(item.ref, item.title)}
tooltip={!expanded ? item.title : undefined}
isActive={activeItemId === item.id}
/>
);
})}
</div>
</CustomScrollbar>
</PanelContainer>

View File

@ -10,7 +10,7 @@ export interface ContentOutlineItemContextProps extends ContentOutlineItemBasePr
type RegisterFunction = ({ title, icon, ref }: Omit<ContentOutlineItemContextProps, 'id'>) => string;
interface ContentOutlineContextProps {
export interface ContentOutlineContextProps {
outlineItems: ContentOutlineItemContextProps[];
register: RegisterFunction;
unregister: (id: string) => void;

View File

@ -9,17 +9,31 @@ type CommonProps = {
icon: string;
tooltip?: string;
className?: string;
isActive?: boolean;
};
export type ContentOutlineItemButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
export function ContentOutlineItemButton({ title, icon, tooltip, className, ...rest }: ContentOutlineItemButtonProps) {
export function ContentOutlineItemButton({
title,
icon,
tooltip,
className,
isActive,
...rest
}: ContentOutlineItemButtonProps) {
const styles = useStyles2(getStyles);
const buttonStyles = cx(styles.button, className);
const body = (
<button className={buttonStyles} aria-label={tooltip} {...rest}>
<button
className={cx(buttonStyles, {
[styles.active]: isActive,
})}
aria-label={tooltip}
{...rest}
>
{renderIcon(icon)}
{title}
</button>
@ -68,5 +82,23 @@ const getStyles = (theme: GrafanaTheme2) => {
textDecoration: 'underline',
},
}),
active: css({
backgroundColor: theme.colors.background.secondary,
borderTopRightRadius: theme.shape.radius.default,
borderBottomRightRadius: theme.shape.radius.default,
position: 'relative',
'&::before': {
backgroundImage: theme.colors.gradients.brandVertical,
borderRadius: theme.shape.radius.default,
content: '" "',
display: 'block',
height: '100%',
position: 'absolute',
transform: 'translateX(-50%)',
width: theme.spacing(0.5),
left: '2px',
},
}),
};
};