mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
40583aec0f
commit
20096259d3
@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useToggle } from 'react-use';
|
import { useToggle, useScroll } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
@ -33,9 +33,12 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement | undefined; panelId: string }) {
|
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 { 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) => {
|
const scrollIntoView = (ref: HTMLElement | null, buttonTitle: string) => {
|
||||||
let scrollValue = 0;
|
let scrollValue = 0;
|
||||||
@ -50,6 +53,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
top: scrollValue,
|
top: scrollValue,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
|
|
||||||
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
reportInteraction('explore_toolbar_contentoutline_clicked', {
|
||||||
item: 'select_section',
|
item: 'select_section',
|
||||||
type: buttonTitle,
|
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 (
|
return (
|
||||||
<PanelContainer className={styles.wrapper} id={panelId}>
|
<PanelContainer className={styles.wrapper} id={panelId}>
|
||||||
<CustomScrollbar>
|
<CustomScrollbar>
|
||||||
@ -77,7 +99,8 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
aria-expanded={expanded}
|
aria-expanded={expanded}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{outlineItems.map((item) => (
|
{outlineItems.map((item) => {
|
||||||
|
return (
|
||||||
<ContentOutlineItemButton
|
<ContentOutlineItemButton
|
||||||
key={item.id}
|
key={item.id}
|
||||||
title={expanded ? item.title : undefined}
|
title={expanded ? item.title : undefined}
|
||||||
@ -85,8 +108,10 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement |
|
|||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
onClick={() => scrollIntoView(item.ref, item.title)}
|
onClick={() => scrollIntoView(item.ref, item.title)}
|
||||||
tooltip={!expanded ? item.title : undefined}
|
tooltip={!expanded ? item.title : undefined}
|
||||||
|
isActive={activeItemId === item.id}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
|
@ -10,7 +10,7 @@ export interface ContentOutlineItemContextProps extends ContentOutlineItemBasePr
|
|||||||
|
|
||||||
type RegisterFunction = ({ title, icon, ref }: Omit<ContentOutlineItemContextProps, 'id'>) => string;
|
type RegisterFunction = ({ title, icon, ref }: Omit<ContentOutlineItemContextProps, 'id'>) => string;
|
||||||
|
|
||||||
interface ContentOutlineContextProps {
|
export interface ContentOutlineContextProps {
|
||||||
outlineItems: ContentOutlineItemContextProps[];
|
outlineItems: ContentOutlineItemContextProps[];
|
||||||
register: RegisterFunction;
|
register: RegisterFunction;
|
||||||
unregister: (id: string) => void;
|
unregister: (id: string) => void;
|
||||||
|
@ -9,17 +9,31 @@ type CommonProps = {
|
|||||||
icon: string;
|
icon: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isActive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContentOutlineItemButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
|
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 styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const buttonStyles = cx(styles.button, className);
|
const buttonStyles = cx(styles.button, className);
|
||||||
|
|
||||||
const body = (
|
const body = (
|
||||||
<button className={buttonStyles} aria-label={tooltip} {...rest}>
|
<button
|
||||||
|
className={cx(buttonStyles, {
|
||||||
|
[styles.active]: isActive,
|
||||||
|
})}
|
||||||
|
aria-label={tooltip}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
{renderIcon(icon)}
|
{renderIcon(icon)}
|
||||||
{title}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
@ -68,5 +82,23 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
textDecoration: 'underline',
|
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',
|
||||||
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user