mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
grafana/ui: Create Tabs component (#21328)
* create tabs component * replace tabs in pageheader * splitting two different types of tabitems * fix index and conditionals in tabs * redo tabs to not render anchor links * rename to className and use cx to combine classes * reverting back to a simpler use case * moving type to types file * fix import * redoing Tabs to simpler composition components * pr feedback * update snapshot * use icon component, added knob for storybook
This commit is contained in:
parent
bf18704490
commit
bb649489c8
25
packages/grafana-ui/src/components/Tabs/Tab.tsx
Normal file
25
packages/grafana-ui/src/components/Tabs/Tab.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { FC } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import { Icon } from '..';
|
||||
import { useTheme } from '../../themes';
|
||||
import { IconType } from '../Icon/types';
|
||||
import { getTabsStyle } from './styles';
|
||||
|
||||
export interface TabProps {
|
||||
label: string;
|
||||
active?: boolean;
|
||||
icon?: IconType;
|
||||
onChangeTab: () => void;
|
||||
}
|
||||
|
||||
export const Tab: FC<TabProps> = ({ label, active, icon, onChangeTab }) => {
|
||||
const theme = useTheme();
|
||||
const tabsStyles = getTabsStyle(theme);
|
||||
|
||||
return (
|
||||
<li className={cx(tabsStyles.tabItem, active && tabsStyles.activeStyle)} onClick={onChangeTab}>
|
||||
{icon && <Icon name={icon} />}
|
||||
{label}
|
||||
</li>
|
||||
);
|
||||
};
|
9
packages/grafana-ui/src/components/Tabs/TabsBar.mdx
Normal file
9
packages/grafana-ui/src/components/Tabs/TabsBar.mdx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Props } from '@storybook/addon-docs/blocks';
|
||||
import { TabsBar } from './TabsBar'
|
||||
|
||||
# TabBar
|
||||
|
||||
A composition component for rendering a TabBar with Tabs for navigation
|
||||
|
||||
|
||||
<Props of={TabBar} />
|
51
packages/grafana-ui/src/components/Tabs/TabsBar.story.tsx
Normal file
51
packages/grafana-ui/src/components/Tabs/TabsBar.story.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { select } from '@storybook/addon-knobs';
|
||||
import { TabsBar } from './TabsBar';
|
||||
import { Tab } from './Tab';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import mdx from './TabsBar.mdx';
|
||||
|
||||
export default {
|
||||
title: 'UI/Tabs/TabsBar',
|
||||
component: TabsBar,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ label: '1st child', key: 'first', hide: false, active: true },
|
||||
{ label: '2nd child', key: 'second', hide: false, active: false },
|
||||
{ label: '3rd child', key: 'third', hide: false, active: false },
|
||||
];
|
||||
|
||||
export const Simple = () => {
|
||||
const VISUAL_GROUP = 'Visual options';
|
||||
// ---
|
||||
const icon = select('Icon', { None: undefined, Heart: 'heart', Star: 'star', User: 'user' }, undefined, VISUAL_GROUP);
|
||||
return (
|
||||
<UseState initialState={tabs}>
|
||||
{(state, updateState) => {
|
||||
return (
|
||||
<TabsBar>
|
||||
{state.map((tab, index) => {
|
||||
return (
|
||||
<Tab
|
||||
key={index}
|
||||
label={tab.label}
|
||||
active={tab.active}
|
||||
icon={icon}
|
||||
onChangeTab={() => updateState(state.map((tab, idx) => ({ ...tab, active: idx === index })))}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
);
|
||||
};
|
15
packages/grafana-ui/src/components/Tabs/TabsBar.tsx
Normal file
15
packages/grafana-ui/src/components/Tabs/TabsBar.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import { useTheme } from '../../themes';
|
||||
import { getTabsStyle } from './styles';
|
||||
|
||||
export interface Props {
|
||||
/** Children should be a single <Tab /> or an array of <Tab /> */
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const TabsBar: FC<Props> = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const tabsStyles = getTabsStyle(theme);
|
||||
|
||||
return <ul className={tabsStyles.tabs}>{children}</ul>;
|
||||
};
|
60
packages/grafana-ui/src/components/Tabs/styles.ts
Normal file
60
packages/grafana-ui/src/components/Tabs/styles.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
import { selectThemeVariant, stylesFactory } from '../../themes';
|
||||
|
||||
export const getTabsStyle = stylesFactory((theme: GrafanaTheme) => {
|
||||
const colors = theme.colors;
|
||||
const tabBorderColor = selectThemeVariant({ dark: colors.dark9, light: colors.gray5 }, theme.type);
|
||||
|
||||
return {
|
||||
tabs: css`
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: flex;
|
||||
`,
|
||||
tabItem: css`
|
||||
list-style: none;
|
||||
padding: 10px 15px 9px;
|
||||
margin-right: ${theme.spacing.md};
|
||||
position: relative;
|
||||
display: block;
|
||||
border: solid transparent;
|
||||
border-width: 0 1px 1px;
|
||||
border-radius: ${theme.border.radius.md} ${theme.border.radius.md} 0 0;
|
||||
color: ${colors.text};
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
margin-right: ${theme.spacing.sm};
|
||||
}
|
||||
|
||||
.gicon {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: ${colors.linkHover};
|
||||
}
|
||||
`,
|
||||
activeStyle: css`
|
||||
border-color: ${colors.orange} ${tabBorderColor} transparent;
|
||||
background: ${colors.pageBg};
|
||||
color: ${colors.link};
|
||||
overflow: hidden;
|
||||
cursor: not-allowed;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
top: 0;
|
||||
background-image: linear-gradient(to right, #f05a28 30%, #fbca0a 99%);
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
@ -48,6 +48,8 @@ export { SetInterval } from './SetInterval/SetInterval';
|
||||
|
||||
export { Table } from './Table/Table';
|
||||
export { TableInputCSV } from './TableInputCSV/TableInputCSV';
|
||||
export { TabsBar } from './Tabs/TabsBar';
|
||||
export { Tab } from './Tabs/Tab';
|
||||
|
||||
// Visualizations
|
||||
export {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { FormEvent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Tab, TabsBar } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { NavModel, NavModelItem, NavModelBreadcrumb } from '@grafana/data';
|
||||
import { CoreEvents } from 'app/types';
|
||||
@ -45,37 +45,30 @@ const SelectNav = ({ main, customCss }: { main: NavModelItem; customCss: string
|
||||
);
|
||||
};
|
||||
|
||||
const Tabs = ({ main, customCss }: { main: NavModelItem; customCss: string }) => {
|
||||
return (
|
||||
<ul className={`gf-tabs ${customCss}`}>
|
||||
{main.children.map((tab, idx) => {
|
||||
if (tab.hideFromTabs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tabClasses = classNames({
|
||||
'gf-tabs-link': true,
|
||||
active: tab.active,
|
||||
});
|
||||
|
||||
return (
|
||||
<li className="gf-tabs-item" key={tab.url}>
|
||||
<a className={tabClasses} target={tab.target} href={tab.url}>
|
||||
<i className={tab.icon} />
|
||||
{tab.text}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
const Navigation = ({ main }: { main: NavModelItem }) => {
|
||||
const goToUrl = (index: number) => {
|
||||
main.children.forEach((child, i) => {
|
||||
if (i === index) {
|
||||
appEvents.emit(CoreEvents.locationChange, { href: child.url });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<SelectNav customCss="page-header__select-nav" main={main} />
|
||||
<Tabs customCss="page-header__tabs" main={main} />
|
||||
<TabsBar>
|
||||
{main.children.map((child, index) => {
|
||||
return (
|
||||
<Tab
|
||||
label={child.text}
|
||||
active={child.active}
|
||||
key={`${child.url}-${index}`}
|
||||
onChangeTab={() => goToUrl(index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
@ -94,20 +94,13 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
</select>
|
||||
</div>
|
||||
<ul
|
||||
className="gf-tabs page-header__tabs"
|
||||
className="css-13jkosq"
|
||||
>
|
||||
<li
|
||||
className="gf-tabs-item"
|
||||
className="css-b418eg"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<a
|
||||
className="gf-tabs-link active"
|
||||
href="Admin"
|
||||
>
|
||||
<i
|
||||
className="icon"
|
||||
/>
|
||||
Admin
|
||||
</a>
|
||||
Admin
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
Loading…
Reference in New Issue
Block a user