Merge pull request #6497 from tudi2d/entity-page-responsiveness
EntityPage responsiveness
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/create-app': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Improve the responsiveness of the EntityPage UI. With this the Header component should scale with the screen size & wrapping should not cause overflowing/blocking of links. Additionally enforce the Pages using the Grid Layout to use it across all screen sizes & to wrap as intended.
|
||||
|
||||
To benefit from the improved responsive layout, the `EntityPage` in existing Backstage applications should be updated to set the `xs` column size on each grid item in the page, as this does not default. For example:
|
||||
|
||||
```diff
|
||||
- <Grid item md={6}>
|
||||
+ <Grid item xs={12} md={6}>
|
||||
```
|
||||
@@ -135,6 +135,13 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* NOTE: This page is designed to work on small screens such as mobile devices.
|
||||
* This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`,
|
||||
* since this does not default. If no breakpoints are used, the items will equitably share the asvailable space.
|
||||
* https://material-ui.com/components/grid/#basic-grid.
|
||||
*/
|
||||
|
||||
export const cicdContent = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isJenkinsAvailable}>
|
||||
@@ -292,10 +299,10 @@ const serviceEntityPage = (
|
||||
|
||||
<EntityLayout.Route path="/api" title="API">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityProvidedApisCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityConsumedApisCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -303,10 +310,10 @@ const serviceEntityPage = (
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -431,15 +438,17 @@ const apiPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12}>
|
||||
<EntityAboutCard />
|
||||
</Grid>
|
||||
<Grid container item md={12}>
|
||||
<Grid item md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
<Grid item xs={12}>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -49,15 +49,17 @@ export const EmptyState = ({ title, description, missing, action }: Props) => {
|
||||
className={classes.root}
|
||||
spacing={2}
|
||||
>
|
||||
<Grid item container direction="column" xs={12} md={6}>
|
||||
<Grid item>
|
||||
<Typography variant="h5">{title}</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="body1">{description}</Typography>
|
||||
</Grid>
|
||||
<Grid item className={classes.action}>
|
||||
{action}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Grid container direction="column">
|
||||
<Grid item xs>
|
||||
<Typography variant="h5">{title}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<Typography variant="body1">{description}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs className={classes.action}>
|
||||
{action}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} className={classes.imageContainer}>
|
||||
|
||||
@@ -29,7 +29,7 @@ const useStyles = makeStyles({
|
||||
generalImg: {
|
||||
width: '95%',
|
||||
zIndex: 2,
|
||||
position: 'absolute',
|
||||
position: 'relative',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, 15%)',
|
||||
|
||||
@@ -16,17 +16,20 @@
|
||||
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { makeStyles, Tooltip, Typography } from '@material-ui/core';
|
||||
import { Box, Grid, makeStyles, Tooltip, Typography } from '@material-ui/core';
|
||||
import React, { CSSProperties, PropsWithChildren, ReactNode } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Link } from '../../components/Link';
|
||||
import { Breadcrumbs } from '../Breadcrumbs';
|
||||
|
||||
const minHeaderHeight = 118;
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
header: {
|
||||
gridArea: 'pageHeader',
|
||||
padding: theme.spacing(3),
|
||||
minHeight: 118,
|
||||
height: 'fit-content',
|
||||
minHeight: minHeaderHeight,
|
||||
width: '100%',
|
||||
boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',
|
||||
position: 'relative',
|
||||
@@ -34,26 +37,21 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
backgroundImage: theme.page.backgroundImage,
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: 'cover',
|
||||
},
|
||||
leftItemsBox: {
|
||||
flex: '1 1 auto',
|
||||
maxWidth: '100%',
|
||||
flexGrow: 1,
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
rightItemsBox: {
|
||||
flex: '0 1 auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
marginRight: theme.spacing(1),
|
||||
width: 'auto',
|
||||
},
|
||||
title: {
|
||||
color: theme.palette.bursts.fontColor,
|
||||
lineHeight: '1.0em',
|
||||
wordBreak: 'break-all',
|
||||
fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))',
|
||||
marginBottom: theme.spacing(1),
|
||||
@@ -199,7 +197,7 @@ export const Header = ({
|
||||
<>
|
||||
<Helmet titleTemplate={titleTemplate} defaultTitle={defaultTitle} />
|
||||
<header style={style} className={classes.header}>
|
||||
<div className={classes.leftItemsBox}>
|
||||
<Box className={classes.leftItemsBox}>
|
||||
<TypeFragment
|
||||
classes={classes}
|
||||
type={type}
|
||||
@@ -212,8 +210,10 @@ export const Header = ({
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
<SubtitleFragment classes={classes} subtitle={subtitle} />
|
||||
</div>
|
||||
<div className={classes.rightItemsBox}>{children}</div>
|
||||
</Box>
|
||||
<Grid container className={classes.rightItemsBox} spacing={4}>
|
||||
{children}
|
||||
</Grid>
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -14,29 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Link, makeStyles, Typography } from '@material-ui/core';
|
||||
import { Link, makeStyles, Typography, Grid } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
textAlign: 'left',
|
||||
margin: theme.spacing(2),
|
||||
display: 'inline-block',
|
||||
},
|
||||
label: {
|
||||
color: '#FFFFFF',
|
||||
color: theme.palette.common.white,
|
||||
fontWeight: 'bold',
|
||||
lineHeight: '16px',
|
||||
letterSpacing: 0,
|
||||
fontSize: 14,
|
||||
height: '16px',
|
||||
marginBottom: 2,
|
||||
fontSize: theme.typography.fontSize,
|
||||
marginBottom: theme.spacing(1) / 2,
|
||||
lineHeight: 1,
|
||||
},
|
||||
value: {
|
||||
color: 'rgba(255, 255, 255, 0.8)',
|
||||
lineHeight: '16px',
|
||||
fontSize: 14,
|
||||
height: '16px',
|
||||
fontSize: theme.typography.fontSize,
|
||||
lineHeight: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -64,9 +60,11 @@ export const HeaderLabel = ({ label, value, url }: HeaderLabelProps) => {
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<span className={classes.root}>
|
||||
<Typography className={classes.label}>{label}</Typography>
|
||||
{url ? <Link href={url}>{content}</Link> : content}
|
||||
</span>
|
||||
<Grid item>
|
||||
<span className={classes.root}>
|
||||
<Typography className={classes.label}>{label}</Typography>
|
||||
{url ? <Link href={url}>{content}</Link> : content}
|
||||
</span>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
+7
@@ -157,6 +157,13 @@ const websiteEntityPage = (
|
||||
</EntityLayout>
|
||||
);
|
||||
|
||||
/**
|
||||
* NOTE: This page is designed to work on small screens such as mobile devices.
|
||||
* This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`,
|
||||
* since this does not default. If no breakpoints are used, the items will equitably share the asvailable space.
|
||||
* https://material-ui.com/components/grid/#basic-grid.
|
||||
*/
|
||||
|
||||
const defaultEntityPage = (
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
|
||||
@@ -59,15 +59,17 @@ const apiPage = (
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityAboutCard />
|
||||
</Grid>
|
||||
<Grid container item md={12}>
|
||||
<Grid item md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
<Grid container>
|
||||
<Grid item md={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -20,9 +20,17 @@ import {
|
||||
RELATION_OWNED_BY,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
useElementFilter,
|
||||
Content,
|
||||
Header,
|
||||
HeaderLabel,
|
||||
Page,
|
||||
Progress,
|
||||
RoutedTabs,
|
||||
} from '@backstage/core-components';
|
||||
import {
|
||||
attachComponentData,
|
||||
IconComponent,
|
||||
useElementFilter,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityContext,
|
||||
@@ -37,14 +45,6 @@ import { useNavigate } from 'react-router';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
|
||||
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
|
||||
import {
|
||||
Content,
|
||||
Header,
|
||||
HeaderLabel,
|
||||
Page,
|
||||
Progress,
|
||||
RoutedTabs,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
type SubRoute = {
|
||||
path: string;
|
||||
@@ -68,12 +68,21 @@ const EntityLayoutTitle = ({
|
||||
}: {
|
||||
title: string;
|
||||
entity: Entity | undefined;
|
||||
}) => (
|
||||
<Box display="inline-flex" alignItems="center" height="1em">
|
||||
{title}
|
||||
{entity && <FavouriteEntity entity={entity} />}
|
||||
</Box>
|
||||
);
|
||||
}) => {
|
||||
return (
|
||||
<Box display="inline-flex" alignItems="center" height="1em" maxWidth="100%">
|
||||
<Box
|
||||
component="span"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
>
|
||||
{title}
|
||||
</Box>
|
||||
{entity && <FavouriteEntity entity={entity} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const headerProps = (
|
||||
paramKind: string | undefined,
|
||||
|
||||
@@ -19,7 +19,12 @@ import { Progress } from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { Button, CircularProgress, useTheme } from '@material-ui/core';
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
makeStyles,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
@@ -45,10 +50,20 @@ type Props = {
|
||||
onReady?: () => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
message: {
|
||||
// `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/word-break
|
||||
wordBreak: 'break-word',
|
||||
overflowWrap: 'anywhere',
|
||||
},
|
||||
}));
|
||||
|
||||
export const Reader = ({ entityId, onReady }: Props) => {
|
||||
const { kind, namespace, name } = entityId;
|
||||
const { '*': path } = useParams();
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
const classes = useStyles();
|
||||
|
||||
const {
|
||||
state,
|
||||
@@ -369,6 +384,7 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
variant="outlined"
|
||||
severity="error"
|
||||
action={<TechDocsBuildLogs buildLog={buildLog} />}
|
||||
classes={{ message: classes.message }}
|
||||
>
|
||||
Building a newer version of this documentation failed.{' '}
|
||||
{syncErrorMessage}
|
||||
@@ -381,6 +397,7 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
variant="outlined"
|
||||
severity="error"
|
||||
action={<TechDocsBuildLogs buildLog={buildLog} />}
|
||||
classes={{ message: classes.message }}
|
||||
>
|
||||
Building a newer version of this documentation failed.{' '}
|
||||
{syncErrorMessage}
|
||||
|
||||
Reference in New Issue
Block a user