mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
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:
parent
8833ac247b
commit
12204708e7
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
`;
|
||||
|
||||
|
|
|
@ -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')}
|
||||
|
|
|
@ -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]};
|
||||
`;
|
||||
|
|
|
@ -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}
|
||||
`;
|
||||
|
|
|
@ -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 })`
|
||||
|
|
Loading…
Reference in a new issue