mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Panel: Use Tabs in panel inspector (#21468)
* replace select with tabs * storybook example * Update snapshot, move styles to component files
This commit is contained in:
parent
962d0f6a0e
commit
6b3041d358
@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import { useTheme } from '../../themes';
|
||||
import { getTabsStyle } from './styles';
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { selectThemeVariant, stylesFactory, useTheme } from '../../themes';
|
||||
|
||||
export interface TabProps {
|
||||
label: string;
|
||||
@ -10,9 +10,61 @@ export interface TabProps {
|
||||
onChangeTab: () => void;
|
||||
}
|
||||
|
||||
const getTabStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const colors = theme.colors;
|
||||
const tabBorderColor = selectThemeVariant({ dark: colors.dark9, light: colors.gray5 }, theme.type);
|
||||
|
||||
return {
|
||||
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%);
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
export const Tab: FC<TabProps> = ({ label, active, icon, onChangeTab }) => {
|
||||
const theme = useTheme();
|
||||
const tabsStyles = getTabsStyle(theme);
|
||||
const tabsStyles = getTabStyles(theme);
|
||||
|
||||
return (
|
||||
<li className={cx(tabsStyles.tabItem, active && tabsStyles.activeStyle)} onClick={onChangeTab}>
|
||||
|
23
packages/grafana-ui/src/components/Tabs/TabContent.tsx
Normal file
23
packages/grafana-ui/src/components/Tabs/TabContent.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { css } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const getTabContentStyle = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
tabContent: css`
|
||||
padding: ${theme.spacing.xs} 0;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
export const TabContent: FC<Props> = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const styles = getTabContentStyle(theme);
|
||||
|
||||
return <div className={styles.tabContent}>{children}</div>;
|
||||
};
|
47
packages/grafana-ui/src/components/Tabs/Tabs.story.tsx
Normal file
47
packages/grafana-ui/src/components/Tabs/Tabs.story.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { UseState } from '../../utils/storybook/UseState';
|
||||
import { TabsBar } from './TabsBar';
|
||||
import { Tab } from './Tab';
|
||||
import { TabContent } from './TabContent';
|
||||
|
||||
export default {
|
||||
title: 'UI/Tabs/TabsExample',
|
||||
decorators: [withCenteredStory],
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ label: '1st child', key: 'first', active: true },
|
||||
{ label: '2nd child', key: 'second', active: false },
|
||||
{ label: '3rd child', key: 'third', active: false },
|
||||
];
|
||||
|
||||
export const Simple = () => {
|
||||
return (
|
||||
<UseState initialState={tabs}>
|
||||
{(state, updateState) => {
|
||||
return (
|
||||
<div>
|
||||
<TabsBar>
|
||||
{state.map((tab, index) => {
|
||||
return (
|
||||
<Tab
|
||||
key={index}
|
||||
label={tab.label}
|
||||
active={tab.active}
|
||||
onChangeTab={() => updateState(state.map((tab, idx) => ({ ...tab, active: idx === index })))}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
<TabContent>
|
||||
{state[0].active && <div>First tab content</div>}
|
||||
{state[1].active && <div>Second tab content</div>}
|
||||
{state[2].active && <div>Third tab content</div>}
|
||||
</TabContent>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</UseState>
|
||||
);
|
||||
};
|
@ -18,9 +18,9 @@ export default {
|
||||
};
|
||||
|
||||
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 },
|
||||
{ label: '1st child', key: 'first', active: true },
|
||||
{ label: '2nd child', key: 'second', active: false },
|
||||
{ label: '3rd child', key: 'third', active: false },
|
||||
];
|
||||
|
||||
export const Simple = () => {
|
||||
|
@ -1,15 +1,35 @@
|
||||
import React, { FC, ReactNode } from 'react';
|
||||
import { useTheme } from '../../themes';
|
||||
import { getTabsStyle } from './styles';
|
||||
import { stylesFactory, useTheme } from '../../themes';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
export interface Props {
|
||||
/** Children should be a single <Tab /> or an array of <Tab /> */
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const getTabsBarStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const colors = theme.colors;
|
||||
|
||||
return {
|
||||
tabsWrapper: css`
|
||||
border-bottom: 1px solid ${colors.pageHeaderBorder};
|
||||
`,
|
||||
tabs: css`
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: flex;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
export const TabsBar: FC<Props> = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const tabsStyles = getTabsStyle(theme);
|
||||
const tabsStyles = getTabsBarStyles(theme);
|
||||
|
||||
return <ul className={tabsStyles.tabs}>{children}</ul>;
|
||||
return (
|
||||
<div className={tabsStyles.tabsWrapper}>
|
||||
<ul className={tabsStyles.tabs}>{children}</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,60 +0,0 @@
|
||||
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%);
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
@ -50,6 +50,7 @@ export { Table } from './Table/Table';
|
||||
export { TableInputCSV } from './TableInputCSV/TableInputCSV';
|
||||
export { TabsBar } from './Tabs/TabsBar';
|
||||
export { Tab } from './Tabs/Tab';
|
||||
export { TabContent } from './Tabs/TabContent';
|
||||
|
||||
// Visualizations
|
||||
export {
|
||||
|
@ -93,19 +93,23 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<ul
|
||||
className="css-13jkosq"
|
||||
<div
|
||||
className="css-yuafq3"
|
||||
>
|
||||
<li
|
||||
className="css-b418eg"
|
||||
onClick={[Function]}
|
||||
<ul
|
||||
className="css-13jkosq"
|
||||
>
|
||||
<i
|
||||
className="icon"
|
||||
/>
|
||||
Admin
|
||||
</li>
|
||||
</ul>
|
||||
<li
|
||||
className="css-b418eg"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="icon"
|
||||
/>
|
||||
Admin
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { JSONFormatter, Drawer, Select, Table } from '@grafana/ui';
|
||||
import { JSONFormatter, Drawer, Select, Table, TabsBar, Tab, TabContent } from '@grafana/ui';
|
||||
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { DataFrame, DataSourceApi, SelectableValue, applyFieldOverrides } from '@grafana/data';
|
||||
import { config } from 'app/core/config';
|
||||
@ -139,9 +139,8 @@ export class PanelInspector extends PureComponent<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ border: '1px solid #666' }}>
|
||||
<Table width={330} height={400} data={processed[selected]} />
|
||||
</div>
|
||||
|
||||
<Table width={330} height={400} data={processed[selected]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -169,19 +168,24 @@ export class PanelInspector extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<Drawer title={panel.title} onClose={this.onDismiss}>
|
||||
<Select options={tabs} value={tabs.find(t => t.value === tab)} onChange={this.onSelectTab} />
|
||||
<TabsBar>
|
||||
{tabs.map(t => {
|
||||
return <Tab label={t.label} active={t.value === tab} onChangeTab={() => this.onSelectTab(t)} />;
|
||||
})}
|
||||
</TabsBar>
|
||||
<TabContent>
|
||||
{tab === InspectTab.Data && this.renderDataTab()}
|
||||
|
||||
{tab === InspectTab.Data && this.renderDataTab()}
|
||||
{tab === InspectTab.Meta && this.renderMetadataInspector()}
|
||||
|
||||
{tab === InspectTab.Meta && this.renderMetadataInspector()}
|
||||
{tab === InspectTab.Issue && this.renderIssueTab()}
|
||||
|
||||
{tab === InspectTab.Issue && this.renderIssueTab()}
|
||||
|
||||
{tab === InspectTab.Raw && (
|
||||
<div>
|
||||
<JSONFormatter json={last} open={2} />
|
||||
</div>
|
||||
)}
|
||||
{tab === InspectTab.Raw && (
|
||||
<div>
|
||||
<JSONFormatter json={last} open={2} />
|
||||
</div>
|
||||
)}
|
||||
</TabContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user