Tabs: Reduce active border from 4 px to 2px (#101888)

* Tabs: Reduce active border from 4 to 2px

* css fixes
pull/101919/head
Torkel Ödegaard 4 months ago committed by GitHub
parent fa809ac417
commit 7ecad55bff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      packages/grafana-data/src/themes/createComponents.ts
  2. 26
      packages/grafana-ui/src/components/TabbedContainer/TabbedContainer.tsx
  3. 6
      packages/grafana-ui/src/components/Tabs/Tab.tsx
  4. 3
      packages/grafana-ui/src/components/Tabs/TabsBar.tsx
  5. 84
      public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx
  6. 3
      public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManagerRenderer.tsx

@ -43,9 +43,6 @@ export interface ThemeComponents {
sidemenu: { sidemenu: {
width: number; width: number;
}; };
menuTabs: {
height: number;
};
horizontalDrawer: { horizontalDrawer: {
defaultHeight: number; defaultHeight: number;
}; };
@ -96,6 +93,7 @@ export function createComponents(colors: ThemeColors, shadows: ThemeShadows): Th
sidemenu: { sidemenu: {
width: 57, width: 57,
}, },
// @ts-expect-error (added here to not crash plugins that might use it)
menuTabs: { menuTabs: {
height: 5, height: 5,
}, },

@ -6,8 +6,9 @@ import { SelectableValue, GrafanaTheme2 } from '@grafana/data';
import { IconButton } from '../../components/IconButton/IconButton'; import { IconButton } from '../../components/IconButton/IconButton';
import { TabsBar, Tab, TabContent } from '../../components/Tabs'; import { TabsBar, Tab, TabContent } from '../../components/Tabs';
import { useStyles2, useTheme2 } from '../../themes'; import { useStyles2 } from '../../themes';
import { IconName } from '../../types/icon'; import { IconName } from '../../types/icon';
import { Box } from '../Layout/Box/Box';
import { ScrollContainer } from '../ScrollContainer/ScrollContainer'; import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
export interface TabConfig { export interface TabConfig {
@ -28,14 +29,11 @@ export interface TabbedContainerProps {
export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose, testId }: TabbedContainerProps) { export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose, testId }: TabbedContainerProps) {
const [activeTab, setActiveTab] = useState(tabs.some((tab) => tab.value === defaultTab) ? defaultTab : tabs[0].value); const [activeTab, setActiveTab] = useState(tabs.some((tab) => tab.value === defaultTab) ? defaultTab : tabs[0].value);
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const theme = useTheme2();
const onSelectTab = (item: SelectableValue<string>) => { const onSelectTab = (item: SelectableValue<string>) => {
setActiveTab(item.value!); setActiveTab(item.value!);
}; };
const autoHeight = `calc(100% - (${theme.spacing(theme.components.menuTabs.height)} + ${theme.spacing(1)}))`;
return ( return (
<div className={styles.container} data-testid={testId}> <div className={styles.container} data-testid={testId}>
<TabsBar className={styles.tabs}> <TabsBar className={styles.tabs}>
@ -48,9 +46,11 @@ export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose, t
icon={t.icon} icon={t.icon}
/> />
))} ))}
<IconButton className={styles.close} onClick={onClose} name="times" tooltip={closeIconTooltip ?? 'Close'} /> <Box grow={1} display="flex" justifyContent="flex-end" paddingRight={1}>
<IconButton size="lg" onClick={onClose} name="times" tooltip={closeIconTooltip ?? 'Close'} />
</Box>
</TabsBar> </TabsBar>
<ScrollContainer height={autoHeight}> <ScrollContainer>
<TabContent className={styles.tabContent}>{tabs.find((t) => t.value === activeTab)?.content}</TabContent> <TabContent className={styles.tabContent}>{tabs.find((t) => t.value === activeTab)?.content}</TabContent>
</ScrollContainer> </ScrollContainer>
</div> </div>
@ -60,21 +60,17 @@ export function TabbedContainer({ tabs, defaultTab, closeIconTooltip, onClose, t
const getStyles = (theme: GrafanaTheme2) => ({ const getStyles = (theme: GrafanaTheme2) => ({
container: css({ container: css({
height: '100%', height: '100%',
display: 'flex',
flexDirection: 'column',
flex: '1 1 0',
minHeight: 0,
}), }),
tabContent: css({ tabContent: css({
padding: theme.spacing(2), padding: theme.spacing(2),
backgroundColor: theme.colors.background.primary, backgroundColor: theme.colors.background.primary,
height: `100%`,
}),
close: css({
position: 'absolute',
right: '16px',
top: '5px',
cursor: 'pointer',
fontSize: theme.typography.size.lg,
}), }),
tabs: css({ tabs: css({
paddingTop: theme.spacing(1), paddingTop: theme.spacing(0.5),
borderColor: theme.colors.border.weak, borderColor: theme.colors.border.weak,
ul: { ul: {
marginLeft: theme.spacing(2), marginLeft: theme.spacing(2),

@ -92,11 +92,11 @@ const getStyles = (theme: GrafanaTheme2) => {
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
padding: theme.spacing(0.5), padding: theme.spacing(0, 0.5),
}), }),
link: css({ link: css({
color: theme.colors.text.secondary, color: theme.colors.text.secondary,
padding: theme.spacing(1, 1.5, 0.5), padding: theme.spacing(1, 1.5, 1),
borderRadius: theme.shape.radius.default, borderRadius: theme.shape.radius.default,
display: 'block', display: 'block',
@ -114,7 +114,7 @@ const getStyles = (theme: GrafanaTheme2) => {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
right: 0, right: 0,
height: '4px', height: '2px',
borderRadius: theme.shape.radius.default, borderRadius: theme.shape.radius.default,
bottom: 0, bottom: 0,
}, },

@ -36,8 +36,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
tabs: css({ tabs: css({
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
height: theme.spacing(theme.components.menuTabs.height), alignItems: 'center',
alignItems: 'stretch',
}), }),
}); });

@ -1,11 +1,9 @@
import { css, cx } from '@emotion/css'; import { cx } from '@emotion/css';
import { useLocation } from 'react-router'; import { useLocation } from 'react-router';
import { GrafanaTheme2, locationUtil, textUtil } from '@grafana/data'; import { locationUtil, textUtil } from '@grafana/data';
import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; import { SceneComponentProps, sceneGraph } from '@grafana/scenes';
import { clearButtonStyles, useElementSelection, useStyles2 } from '@grafana/ui'; import { Tab, useElementSelection } from '@grafana/ui';
// eslint-disable-next-line no-restricted-imports
import { getFocusStyles } from '@grafana/ui/src/themes/mixins';
import { TabItem } from './TabItem'; import { TabItem } from './TabItem';
@ -19,87 +17,19 @@ export function TabItemRenderer({ model }: SceneComponentProps<TabItem>) {
const isActive = myIndex === currentTabIndex; const isActive = myIndex === currentTabIndex;
const location = useLocation(); const location = useLocation();
const href = textUtil.sanitize(locationUtil.getUrlForPartial(location, { tab: myIndex })); const href = textUtil.sanitize(locationUtil.getUrlForPartial(location, { tab: myIndex }));
const styles = useStyles2(getStyles);
const clearStyles = useStyles2(clearButtonStyles);
return ( return (
<> <Tab
<div
className={cx( className={cx(
styles.container,
isSelected && 'dashboard-selected-element', isSelected && 'dashboard-selected-element',
isSelectable && !isSelected && 'dashboard-selectable-element' isSelectable && !isSelected && 'dashboard-selectable-element'
)} )}
active={isActive}
role="presentation" role="presentation"
>
<a
href={href} href={href}
className={cx(clearStyles, styles.label, isActive ? styles.labelActive : styles.labelNotActive)}
role="tab"
aria-selected={isActive} aria-selected={isActive}
onPointerDown={onSelect} onPointerDown={onSelect}
> label={titleInterpolated}
{titleInterpolated} />
</a>
</div>
</>
); );
} }
function getStyles(theme: GrafanaTheme2) {
return {
container: css({
listStyle: 'none',
position: 'relative',
display: 'flex',
whiteSpace: 'nowrap',
alignItems: 'center',
}),
checkboxWrapper: css({
paddingLeft: theme.spacing(1),
}),
label: css({
color: theme.colors.text.secondary,
padding: theme.spacing(1, 2, 0.5),
borderRadius: theme.shape.radius.default,
userSelect: 'none',
display: 'block',
height: '100%',
svg: {
marginRight: theme.spacing(1),
},
'&:focus-visible': getFocusStyles(theme),
'&::before': {
display: 'block',
content: '" "',
position: 'absolute',
left: 0,
right: 0,
height: '4px',
borderRadius: theme.shape.radius.default,
bottom: 0,
},
}),
labelNotActive: css({
'&:hover, &:focus': {
color: theme.colors.text.primary,
'&::before': {
backgroundColor: theme.colors.action.hover,
},
},
}),
labelActive: css({
color: theme.colors.text.primary,
overflow: 'hidden',
'&::before': {
backgroundImage: theme.colors.gradients.brandHorizontal,
},
}),
};
}

@ -60,12 +60,13 @@ const getStyles = (theme: GrafanaTheme2) => ({
overflowX: 'auto', overflowX: 'auto',
overflowY: 'hidden', overflowY: 'hidden',
paddingInline: theme.spacing(0.125), paddingInline: theme.spacing(0.125),
paddingTop: '1px',
}), }),
tabContentContainer: css({ tabContentContainer: css({
backgroundColor: 'transparent', backgroundColor: 'transparent',
display: 'flex', display: 'flex',
flex: 1, flex: 1,
minHeight: 0, minHeight: 0,
padding: '2px 2px 0 2px', paddingTop: theme.spacing(1),
}), }),
}); });

Loading…
Cancel
Save