diff --git a/packages/grafana-ui/src/components/Tabs/Tab.tsx b/packages/grafana-ui/src/components/Tabs/Tab.tsx new file mode 100644 index 00000000000..1c2bbadaafc --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/Tab.tsx @@ -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 = ({ label, active, icon, onChangeTab }) => { + const theme = useTheme(); + const tabsStyles = getTabsStyle(theme); + + return ( +
  • + {icon && } + {label} +
  • + ); +}; diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.mdx b/packages/grafana-ui/src/components/Tabs/TabsBar.mdx new file mode 100644 index 00000000000..0ec3f6d6bed --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.mdx @@ -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 + + + diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.story.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.story.tsx new file mode 100644 index 00000000000..34287fa0004 --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.story.tsx @@ -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 ( + + {(state, updateState) => { + return ( + + {state.map((tab, index) => { + return ( + updateState(state.map((tab, idx) => ({ ...tab, active: idx === index })))} + /> + ); + })} + + ); + }} + + ); +}; diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx new file mode 100644 index 00000000000..5c95b769511 --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx @@ -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 or an array of */ + children: ReactNode; +} + +export const TabsBar: FC = ({ children }) => { + const theme = useTheme(); + const tabsStyles = getTabsStyle(theme); + + return
      {children}
    ; +}; diff --git a/packages/grafana-ui/src/components/Tabs/styles.ts b/packages/grafana-ui/src/components/Tabs/styles.ts new file mode 100644 index 00000000000..48a8f782595 --- /dev/null +++ b/packages/grafana-ui/src/components/Tabs/styles.ts @@ -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%); + } + `, + }; +}); diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 4783aebbdbb..356fcb161cd 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -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 { diff --git a/public/app/core/components/PageHeader/PageHeader.tsx b/public/app/core/components/PageHeader/PageHeader.tsx index 65d9e13c4e1..44ef6b20488 100644 --- a/public/app/core/components/PageHeader/PageHeader.tsx +++ b/public/app/core/components/PageHeader/PageHeader.tsx @@ -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 ( -
      - {main.children.map((tab, idx) => { - if (tab.hideFromTabs) { - return null; - } - - const tabClasses = classNames({ - 'gf-tabs-link': true, - active: tab.active, - }); - - return ( -
    • - - - {tab.text} - -
    • - ); - })} -
    - ); -}; - 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 ( ); }; diff --git a/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap index af6321ddd7d..5a6da0ad627 100644 --- a/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap +++ b/public/app/features/admin/__snapshots__/ServerStats.test.tsx.snap @@ -94,20 +94,13 @@ exports[`ServerStats Should render table with stats 1`] = `