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 React, { FC } from 'react';
|
||||||
import { cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { useTheme } from '../../themes';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { getTabsStyle } from './styles';
|
import { selectThemeVariant, stylesFactory, useTheme } from '../../themes';
|
||||||
|
|
||||||
export interface TabProps {
|
export interface TabProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -10,9 +10,61 @@ export interface TabProps {
|
|||||||
onChangeTab: () => void;
|
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 }) => {
|
export const Tab: FC<TabProps> = ({ label, active, icon, onChangeTab }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const tabsStyles = getTabsStyle(theme);
|
const tabsStyles = getTabStyles(theme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={cx(tabsStyles.tabItem, active && tabsStyles.activeStyle)} onClick={onChangeTab}>
|
<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 = [
|
const tabs = [
|
||||||
{ label: '1st child', key: 'first', hide: false, active: true },
|
{ label: '1st child', key: 'first', active: true },
|
||||||
{ label: '2nd child', key: 'second', hide: false, active: false },
|
{ label: '2nd child', key: 'second', active: false },
|
||||||
{ label: '3rd child', key: 'third', hide: false, active: false },
|
{ label: '3rd child', key: 'third', active: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Simple = () => {
|
export const Simple = () => {
|
||||||
|
@ -1,15 +1,35 @@
|
|||||||
import React, { FC, ReactNode } from 'react';
|
import React, { FC, ReactNode } from 'react';
|
||||||
import { useTheme } from '../../themes';
|
import { stylesFactory, useTheme } from '../../themes';
|
||||||
import { getTabsStyle } from './styles';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
/** Children should be a single <Tab /> or an array of <Tab /> */
|
/** Children should be a single <Tab /> or an array of <Tab /> */
|
||||||
children: ReactNode;
|
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 }) => {
|
export const TabsBar: FC<Props> = ({ children }) => {
|
||||||
const theme = useTheme();
|
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 { TableInputCSV } from './TableInputCSV/TableInputCSV';
|
||||||
export { TabsBar } from './Tabs/TabsBar';
|
export { TabsBar } from './Tabs/TabsBar';
|
||||||
export { Tab } from './Tabs/Tab';
|
export { Tab } from './Tabs/Tab';
|
||||||
|
export { TabContent } from './Tabs/TabContent';
|
||||||
|
|
||||||
// Visualizations
|
// Visualizations
|
||||||
export {
|
export {
|
||||||
|
@ -93,19 +93,23 @@ exports[`ServerStats Should render table with stats 1`] = `
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<div
|
||||||
className="css-13jkosq"
|
className="css-yuafq3"
|
||||||
>
|
>
|
||||||
<li
|
<ul
|
||||||
className="css-b418eg"
|
className="css-13jkosq"
|
||||||
onClick={[Function]}
|
|
||||||
>
|
>
|
||||||
<i
|
<li
|
||||||
className="icon"
|
className="css-b418eg"
|
||||||
/>
|
onClick={[Function]}
|
||||||
Admin
|
>
|
||||||
</li>
|
<i
|
||||||
</ul>
|
className="icon"
|
||||||
|
/>
|
||||||
|
Admin
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
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 { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||||
import { DataFrame, DataSourceApi, SelectableValue, applyFieldOverrides } from '@grafana/data';
|
import { DataFrame, DataSourceApi, SelectableValue, applyFieldOverrides } from '@grafana/data';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
@ -139,9 +139,8 @@ export class PanelInspector extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ border: '1px solid #666' }}>
|
|
||||||
<Table width={330} height={400} data={processed[selected]} />
|
<Table width={330} height={400} data={processed[selected]} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -169,19 +168,24 @@ export class PanelInspector extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer title={panel.title} onClose={this.onDismiss}>
|
<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>
|
||||||
{tab === InspectTab.Raw && (
|
<JSONFormatter json={last} open={2} />
|
||||||
<div>
|
</div>
|
||||||
<JSONFormatter json={last} open={2} />
|
)}
|
||||||
</div>
|
</TabContent>
|
||||||
)}
|
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user