Commit 615b000a by Tobias Skarhed Committed by GitHub

Tab: Make active tab clickable and add hyperlink functionality (#25546)

* onChangeTab for active tab and pointer mouse

* Add anchor element to tab

* Make a strict 'anchor' mode

* Add short docs

* Apply suggestions from code review

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>

* Fix nits

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
parent e8e280c8
...@@ -11,31 +11,41 @@ import { Counter } from './Counter'; ...@@ -11,31 +11,41 @@ import { Counter } from './Counter';
export interface TabProps extends HTMLProps<HTMLLIElement> { export interface TabProps extends HTMLProps<HTMLLIElement> {
label: string; label: string;
active?: boolean; active?: boolean;
/** When provided, it is possible to use the tab as a hyperlink. Use in cases where the tabs update location. */
href?: string;
icon?: IconName; icon?: IconName;
onChangeTab: () => void; onChangeTab: (event?: React.MouseEvent<HTMLLIElement>) => void;
/** A number rendered next to the text. Usually used to display the number of items in a tab's view. */
counter?: number; counter?: number;
} }
export const Tab = React.forwardRef<HTMLLIElement, TabProps>( export const Tab = React.forwardRef<HTMLLIElement, TabProps>(
({ label, active, icon, onChangeTab, counter, className, ...otherProps }, ref) => { ({ label, active, icon, onChangeTab, counter, className, href, ...otherProps }, ref) => {
const theme = useTheme(); const theme = useTheme();
const tabsStyles = getTabStyles(theme); const tabsStyles = getTabStyles(theme);
const content = () => (
<>
{icon && <Icon name={icon} />}
{label}
{typeof counter === 'number' && <Counter value={counter} />}
</>
);
return ( return (
<li <li
{...otherProps} {...otherProps}
className={cx(tabsStyles.tabItem, active && tabsStyles.activeStyle)} className={cx(!href && tabsStyles.padding, tabsStyles.tabItem, active && tabsStyles.activeStyle)}
onClick={() => { onClick={onChangeTab}
if (!active) {
onChangeTab();
}
}}
aria-label={otherProps['aria-label'] || selectors.components.Tab.title(label)} aria-label={otherProps['aria-label'] || selectors.components.Tab.title(label)}
ref={ref} ref={ref}
> >
{icon && <Icon name={icon} />} {href ? (
{label} <a href={href} className={tabsStyles.padding}>
{typeof counter === 'number' && <Counter value={counter} />} {content()}
</a>
) : (
<>{content()}</>
)}
</li> </li>
); );
} }
...@@ -47,7 +57,6 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -47,7 +57,6 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
tabItem: css` tabItem: css`
list-style: none; list-style: none;
padding: 11px 15px 9px;
margin-right: ${theme.spacing.md}; margin-right: ${theme.spacing.md};
position: relative; position: relative;
display: block; display: block;
...@@ -61,18 +70,24 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -61,18 +70,24 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme) => {
margin-right: ${theme.spacing.sm}; margin-right: ${theme.spacing.sm};
} }
a {
display: block;
height: 100%;
}
&:hover, &:hover,
&:focus { &:focus {
color: ${colors.linkHover}; color: ${colors.linkHover};
} }
`, `,
padding: css`
padding: 11px 15px 9px;
`,
activeStyle: css` activeStyle: css`
label: activeTabStyle; label: activeTabStyle;
border-color: ${theme.palette.orange} ${colors.pageHeaderBorder} transparent; border-color: ${theme.palette.orange} ${colors.pageHeaderBorder} transparent;
background: ${colors.bodyBg}; background: ${colors.bodyBg};
color: ${colors.link}; color: ${colors.link};
overflow: hidden; overflow: hidden;
cursor: default;
&::before { &::before {
display: block; display: block;
......
import { Props } from '@storybook/addon-docs/blocks'; import { Props } from '@storybook/addon-docs/blocks';
import { TabsBar } from './TabsBar' import { TabsBar } from './TabsBar';
# TabBar # TabBar
A composition component for rendering a TabBar with Tabs for navigation A composition component for rendering a TabBar with Tabs for navigation.
It has two modes - navigation and onClick. Navigation renders it as a simple `<a>` element. To enable it, use the `href` prop. The onClick mode uses an onClick handler instead. To enable it, use the `onChangeTab` prop.
**Warning!** Using `href` and `onChangeTab` at the same time may have unintended consequences.
<Props of={TabsBar} /> <Props of={TabsBar} />
...@@ -79,6 +79,7 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => { ...@@ -79,6 +79,7 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => {
key={`${child.url}-${index}`} key={`${child.url}-${index}`}
icon={child.icon as IconName} icon={child.icon as IconName}
onChangeTab={() => goToUrl(index)} onChangeTab={() => goToUrl(index)}
href={child.url}
/> />
) )
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment