From 20096259d3891202c0ce29e6b32d6fa6516658b9 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 4 Jan 2024 08:15:50 -0700 Subject: [PATCH] Explore: Add active state to ContentOutlineItemButton (#78779) * Add active state to ContentOutlineItemButton * Improve * WIP: improve * Cleanup * Fix * Improve betterer, remove extra curly braces --- .../explore/ContentOutline/ContentOutline.tsx | 53 ++++++++++++++----- .../ContentOutline/ContentOutlineContext.tsx | 2 +- .../ContentOutlineItemButton.tsx | 36 ++++++++++++- 3 files changed, 74 insertions(+), 17 deletions(-) diff --git a/public/app/features/explore/ContentOutline/ContentOutline.tsx b/public/app/features/explore/ContentOutline/ContentOutline.tsx index 01b9be893ae..aa604d6d792 100644 --- a/public/app/features/explore/ContentOutline/ContentOutline.tsx +++ b/public/app/features/explore/ContentOutline/ContentOutline.tsx @@ -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(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 ( @@ -77,16 +99,19 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement | aria-expanded={expanded} /> - {outlineItems.map((item) => ( - scrollIntoView(item.ref, item.title)} - tooltip={!expanded ? item.title : undefined} - /> - ))} + {outlineItems.map((item) => { + return ( + scrollIntoView(item.ref, item.title)} + tooltip={!expanded ? item.title : undefined} + isActive={activeItemId === item.id} + /> + ); + })} diff --git a/public/app/features/explore/ContentOutline/ContentOutlineContext.tsx b/public/app/features/explore/ContentOutline/ContentOutlineContext.tsx index 5ca319431e1..f5a145c6965 100644 --- a/public/app/features/explore/ContentOutline/ContentOutlineContext.tsx +++ b/public/app/features/explore/ContentOutline/ContentOutlineContext.tsx @@ -10,7 +10,7 @@ export interface ContentOutlineItemContextProps extends ContentOutlineItemBasePr type RegisterFunction = ({ title, icon, ref }: Omit) => string; -interface ContentOutlineContextProps { +export interface ContentOutlineContextProps { outlineItems: ContentOutlineItemContextProps[]; register: RegisterFunction; unregister: (id: string) => void; diff --git a/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx b/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx index 7f887a76899..febd0e6d009 100644 --- a/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx +++ b/public/app/features/explore/ContentOutline/ContentOutlineItemButton.tsx @@ -9,17 +9,31 @@ type CommonProps = { icon: string; tooltip?: string; className?: string; + isActive?: boolean; }; export type ContentOutlineItemButtonProps = CommonProps & ButtonHTMLAttributes; -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 = ( - @@ -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', + }, + }), }; };