Connect: Make tabs shadows look better (#27931)

* Add bottom shadow for inactive tabs and inset for the active one

* Add shadow for new tab item by using common styling

* Adjust `KeyboardShortcutsPanel` to the light theme

* Center Servers/Databases/Kubes navbar vertically between tabs and table by using the same margin values (8px)

* Set title on the element that is dragged
This commit is contained in:
Grzegorz Zdunek 2023-06-20 10:57:59 +02:00 committed by GitHub
parent 8833ac247b
commit 12204708e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 118 deletions

View file

@ -82,7 +82,7 @@ export function Cluster() {
}
return (
<Layout mx="auto" px={5} pt={3} height="100%">
<Layout mx="auto" px={5} pt={2} height="100%">
<ClusterResources />
</Layout>
);

View file

@ -73,10 +73,17 @@ export function KeyboardShortcutsPanel() {
function Entry(props: { title: string; accelerator: string }) {
return (
<>
<Text textAlign="right" color="light" typography="subtitle1" py="4px">
<Text textAlign="right" typography="subtitle1" py="4px">
{props.title}
</Text>
<MonoText bg="levels.surface" textAlign="left" px="12px" py="4px">
<MonoText
css={`
background: ${props => props.theme.colors.spotBackground[0]};
`}
textAlign="left"
px="12px"
py="4px"
>
{props.accelerator}
</MonoText>
</>
@ -84,9 +91,7 @@ function Entry(props: { title: string; accelerator: string }) {
}
const MonoText = styled(Text)`
font-family: ${props => props.theme.fonts.mono};
width: fit-content;
opacity: 0.7;
border-radius: 4px;
`;

View file

@ -106,7 +106,6 @@ export function TabHost({ ctx }: { ctx: IAppContext }) {
onContextMenu={handleTabContextMenu}
activeTab={activeDocument?.uri}
onMoved={handleTabMoved}
disableNew={false}
onNew={openClusterTab}
newTabTooltip={getLabelWithAccelerator('New Tab', 'newTab')}
closeTabTooltip={getLabelWithAccelerator('Close', 'closeTab')}

View file

@ -16,7 +16,7 @@ limitations under the License.
import React, { useRef } from 'react';
import styled from 'styled-components';
import { Close as CloseIcon } from 'design/Icon';
import * as Icons from 'design/Icon';
import { ButtonIcon, Text } from 'design';
import LinearProgress from 'teleterm/ui/components/LinearProgress';
@ -27,6 +27,7 @@ type TabItemProps = {
index?: number;
name?: string;
active?: boolean;
nextActive?: boolean;
closeTabTooltip?: string;
isLoading?: boolean;
onClick?(): void;
@ -39,6 +40,7 @@ export function TabItem(props: TabItemProps) {
const {
name,
active,
nextActive,
onClick,
onClose,
index,
@ -62,71 +64,104 @@ export function TabItem(props: TabItemProps) {
};
return (
<StyledTabItem
<RelativeContainer
onClick={onClick}
onContextMenu={onContextMenu}
ref={ref}
active={active}
dragging={isDragging}
title={name}
canDrag={canDrag}
css={`
flex-grow: 1;
min-width: 0;
`}
>
<Title color="inherit" fontWeight={700} fontSize="12px">
{name}
</Title>
{isLoading && active && <LinearProgress transparentBackground={true} />}
{onClose && (
<ButtonIcon
size={0}
mr={1}
title={closeTabTooltip}
css={`
transition: none;
`}
onClick={handleClose}
>
<CloseIcon fontSize="16px" />
</ButtonIcon>
)}
</StyledTabItem>
<TabContent
ref={ref}
active={active}
dragging={isDragging}
canDrag={canDrag}
title={name}
>
<Title color="inherit" fontWeight={700} fontSize="12px">
{name}
</Title>
{isLoading && active && <LinearProgress transparentBackground={true} />}
{onClose && (
<ButtonIcon
size={0}
mr={1}
title={closeTabTooltip}
css={`
transition: none;
`}
onClick={handleClose}
>
<Icons.Close fontSize="16px" />
</ButtonIcon>
)}
</TabContent>
{!active && !nextActive && <Separator />}
{(!active || isDragging) && <BottomShadow />}
</RelativeContainer>
);
}
const StyledTabItem = styled.div(({ theme, active, dragging, canDrag }) => {
const styles: any = {
display: 'flex',
flexBasis: '0',
flexGrow: '1',
opacity: '1',
color: theme.colors.text.slightlyMuted,
alignItems: 'center',
minWidth: '0',
height: '100%',
border: 'none',
borderRadius: '8px 8px 0 0',
'&:hover, &:focus': {
color: theme.colors.text.main,
transition: 'color .3s',
},
position: 'relative',
};
type NewTabItemProps = {
tooltip: string;
onClick(): void;
};
if (active) {
styles['backgroundColor'] = theme.colors.levels.sunken;
styles['color'] = theme.colors.text.main;
styles['transition'] = 'none';
export function NewTabItem(props: NewTabItemProps) {
return (
<RelativeContainer>
<TabContent active={false}>
<ButtonIcon
ml="1"
mr="2"
size={0}
title={props.tooltip}
onClick={props.onClick}
>
<Icons.Add fontSize="16px" />
</ButtonIcon>
</TabContent>
<BottomShadow />
</RelativeContainer>
);
}
const RelativeContainer = styled.div`
position: relative;
display: flex;
flex-basis: 0;
align-items: center;
height: 100%;
`;
const TabContent = styled.div`
display: flex;
z-index: 1; // covers shadow from the top
align-items: center;
min-width: 0;
width: 100%;
height: 100%;
border-radius: 8px 8px 0 0;
position: relative;
opacity: ${props => (props.dragging ? 0 : 1)};
color: ${props =>
props.active
? props.theme.colors.text.main
: props.theme.colors.text.slightlyMuted};
background: ${props =>
props.active
? props.theme.colors.levels.sunken
: props.theme.colors.levels.surface};
box-shadow: ${props =>
props.active ? 'inset 0px 2px 1.5px -1px rgba(0, 0, 0, 0.12)' : undefined};
&:hover,
&:focus {
color: ${props => props.theme.colors.text.main};
transition: color 0.3s;
}
if (dragging) {
styles['opacity'] = 0;
}
if (canDrag) {
styles['cursor'] = 'pointer';
}
return styles;
});
`;
const Title = styled(Text)`
display: block;
@ -142,3 +177,23 @@ const Title = styled(Text)`
min-width: 0;
width: 100%;
`;
const BottomShadow = styled.div`
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1.5px rgba(0, 0, 0, 0.13),
0 1px 4px rgba(0, 0, 0, 0.12);
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: inherit;
`;
const Separator = styled.div`
height: 23px;
width: 1px;
position: absolute;
z-index: 1;
right: 0;
background: ${props => props.theme.colors.spotBackground[2]};
`;

View file

@ -14,15 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { Fragment } from 'react';
import React from 'react';
import styled from 'styled-components';
import { typography } from 'design/system';
import { Box, ButtonIcon } from 'design';
import * as Icons from 'design/Icon';
import { Box } from 'design';
import { Document } from 'teleterm/ui/services/workspacesService';
import { TabItem } from './TabItem';
import { TabItem, NewTabItem } from './TabItem';
export function Tabs(props: Props) {
const {
@ -31,7 +30,6 @@ export function Tabs(props: Props) {
onSelect,
onClose,
onNew,
disableNew,
onMoved,
onContextMenu,
newTabTooltip,
@ -39,48 +37,34 @@ export function Tabs(props: Props) {
...styledProps
} = props;
const $emptyTab = (
<>
<TabItem active={true} />
<Separator />
</>
const $items = items.length ? (
items.map((item, index) => {
const active = item.uri === activeTab;
const nextActive = items[index + 1]?.uri === activeTab;
return (
<TabItem
key={item.uri}
index={index}
name={item.title}
active={active}
nextActive={nextActive}
onClick={() => onSelect(item)}
onClose={() => onClose(item)}
onContextMenu={() => onContextMenu(item)}
onMoved={onMoved}
isLoading={getIsLoading(item)}
closeTabTooltip={closeTabTooltip}
/>
);
})
) : (
<TabItem active={true} />
);
const $items = items.length
? items.map((item, index) => {
const active = item.uri === activeTab;
return (
<Fragment key={item.uri}>
<TabItem
index={index}
name={item.title}
active={active}
onClick={() => onSelect(item)}
onClose={() => onClose(item)}
onContextMenu={() => onContextMenu(item)}
onMoved={onMoved}
isLoading={getIsLoading(item)}
closeTabTooltip={closeTabTooltip}
/>
<Separator />
</Fragment>
);
})
: $emptyTab;
return (
<StyledTabs as="nav" typography="h5" bold {...styledProps}>
{$items}
<ButtonIcon
ml="1"
mr="2"
size={0}
disabled={disableNew}
title={newTabTooltip}
onClick={onNew}
>
<Icons.Add fontSize="16px" />
</ButtonIcon>
<NewTabItem tooltip={newTabTooltip} onClick={onNew} />
</StyledTabs>
);
}
@ -92,7 +76,6 @@ function getIsLoading(doc: Document): boolean {
type Props = {
items: Document[];
activeTab: string;
disableNew: boolean;
newTabTooltip: string;
closeTabTooltip: string;
onNew: () => void;
@ -102,13 +85,6 @@ type Props = {
[index: string]: any;
};
const Separator = styled.div`
height: 23px;
width: 1px;
margin: 0 1px;
background: ${props => props.theme.colors.spotBackground[2]};
`;
const StyledTabs = styled(Box)`
background-color: ${props => props.theme.colors.levels.surface};
min-height: 32px;
@ -116,10 +92,8 @@ const StyledTabs = styled(Box)`
flex-wrap: nowrap;
align-items: center;
flex-shrink: 0;
overflow: hidden;
max-width: 100%;
position: relative;
z-index: 1;
box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.2);
${typography}
`;

View file

@ -49,7 +49,6 @@ const Grid = styled(Flex).attrs({ gap: 3, py: 2, px: 3 })`
height: 56px;
align-items: center;
justify-content: space-between;
z-index: 2; // minimally higher z-index than the one defined in StyledTabs, so that its drop-shadow doesn't cover the TopBar
`;
const CentralContainer = styled(Flex).attrs({ gap: 3 })`