Switch from ReactJSS to styled-components in ui-components

This commit is contained in:
Florian Scholdei
2019-10-08 16:42:08 +02:00
parent 7ec1a8dd01
commit 1b6392defc
19 changed files with 422 additions and 566 deletions

View File

@@ -1,41 +1,34 @@
// @flow
//@flow
import React from "react";
import type { Branch } from "@scm-manager/ui-types";
import injectSheet from "react-jss";
import classNames from "classnames";
import styled from "styled-components";
import type { Branch } from "@scm-manager/ui-types";
import DropDown from "./forms/DropDown";
const styles = {
zeroflex: {
flexBasis: "inherit",
flexGrow: 0
},
minWidthOfControl: {
minWidth: "10rem"
},
labelSizing: {
fontSize: "1rem !important"
},
noBottomMargin: {
marginBottom: "0 !important"
}
};
type Props = {
branches: Branch[], // TODO: Use generics?
branches: Branch[],
selected: (branch?: Branch) => void,
selectedBranch?: string,
label: string,
disabled?: boolean,
// context props
classes: Object
disabled?: boolean
};
type State = { selectedBranch?: Branch };
class BranchSelector extends React.Component<Props, State> {
const ZeroflexFieldLabel = styled.label`
flex-basis: inherit;
flex-grow: 0;
`;
const MinWidthControl = styled.div`
min-width: 10rem;
`;
const NoBottomMarginField = styled.div`
margin-bottom: 0 !important;
`;
export default class BranchSelector extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
@@ -52,32 +45,19 @@ class BranchSelector extends React.Component<Props, State> {
}
render() {
const { branches, classes, label, disabled } = this.props;
const { branches, label, disabled } = this.props;
if (branches) {
return (
<div
className={classNames(
"field",
"is-horizontal"
)}
>
<div
className={classNames("field-label", "is-normal", classes.zeroflex)}
<div className={classNames("field", "is-horizontal")}>
<ZeroflexFieldLabel
className={classNames("field-label", "is-normal")}
>
<label className={classNames("label", classes.labelSizing)}>
{label}
</label>
</div>
<div className={classNames("label", "is-size-6")}>{label}</div>
</ZeroflexFieldLabel>
<div className="field-body">
<div
className={classNames(
"field",
"is-narrow",
classes.noBottomMargin
)}
>
<div className={classNames("control", classes.minWidthOfControl)}>
<NoBottomMarginField className={classNames("field", "is-narrow")}>
<MinWidthControl classname="control">
<DropDown
className="is-fullwidth"
options={branches.map(b => b.name)}
@@ -89,8 +69,8 @@ class BranchSelector extends React.Component<Props, State> {
: ""
}
/>
</div>
</div>
</MinWidthControl>
</NoBottomMarginField>
</div>
</div>
);
@@ -113,5 +93,3 @@ class BranchSelector extends React.Component<Props, State> {
this.setState({ selectedBranch: branch });
};
}
export default injectSheet(styles)(BranchSelector);

View File

@@ -2,8 +2,8 @@
import React from "react";
import { Link } from "react-router-dom";
import { translate } from "react-i18next";
import injectSheet from "react-jss";
import classNames from "classnames";
import styled from "styled-components";
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Branch, Repository } from "@scm-manager/ui-types";
import Icon from "./Icon";
@@ -18,29 +18,21 @@ type Props = {
baseUrl: string,
// Context props
classes: any,
t: string => string
};
const styles = {
noMargin: {
margin: "0"
},
flexRow: {
display: "flex",
flexDirection: "row"
},
flexStart: {
flex: "1"
},
homeIcon: {
lineHeight: "1.5rem"
},
buttonGroup: {
alignSelf: "center",
paddingRight: "1rem"
}
};
const FlexStartNav = styled.nav`
flex: 1;
`;
const HomeIcon = styled(Icon)`
line-height: 1.5rem;
`;
const ActionWrapper = styled.div`
align-self: center;
padding-right: 1rem;
`;
class Breadcrumb extends React.Component<Props> {
renderPath() {
@@ -79,25 +71,20 @@ class Breadcrumb extends React.Component<Props> {
revision,
path,
repository,
classes,
t
} = this.props;
return (
<>
<div className={classes.flexRow}>
<nav
className={classNames(
classes.flexStart,
"breadcrumb sources-breadcrumb"
)}
<div className="is-flex">
<FlexStartNav
className={classNames("breadcrumb", "sources-breadcrumb")}
aria-label="breadcrumbs"
>
<ul>
<li>
<Link to={baseUrl + "/" + revision + "/"}>
<Icon
className={classes.homeIcon}
<HomeIcon
title={t("breadcrumb.home")}
name="home"
color="inherit"
@@ -106,9 +93,9 @@ class Breadcrumb extends React.Component<Props> {
</li>
{this.renderPath()}
</ul>
</nav>
</FlexStartNav>
{binder.hasExtension("repos.sources.actionbar") && (
<div className={classes.buttonGroup}>
<ActionWrapper>
<ExtensionPoint
name="repos.sources.actionbar"
props={{
@@ -124,13 +111,13 @@ class Breadcrumb extends React.Component<Props> {
}}
renderAll={true}
/>
</div>
</ActionWrapper>
)}
</div>
<hr className={classes.noMargin} />
<hr className="is-marginless" />
</>
);
}
}
export default translate("commons")(injectSheet(styles)(Breadcrumb));
export default translate("commons")(Breadcrumb);

View File

@@ -1,45 +1,9 @@
//@flow
import * as React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
import styled from "styled-components";
import { Link } from "react-router-dom";
const styles = {
inner: {
position: "relative",
pointerEvents: "none",
zIndex: 1
},
innerLink: {
pointerEvents: "all"
},
centerImage: {
marginTop: "0.8em",
marginLeft: "1em !important"
},
flexFullHeight: {
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignSelf: "stretch"
},
footer: {
display: "flex",
paddingBottom: "1rem",
},
topPart: {
display: "flex"
},
contentRight: {
marginLeft: "auto"
},
contentLeft: {
marginBottom: "0 !important",
overflow: "hidden"
}
};
type Props = {
title: string,
description: string,
@@ -49,19 +13,54 @@ type Props = {
footerRight: React.Node,
link?: string,
action?: () => void,
className?: string,
// context props
classes: any
className?: string
};
class CardColumn extends React.Component<Props> {
const NoEventWrapper = styled.article`
pointer-events: none;
z-index: 1;
`;
const AvatarWrapper = styled.figure`
margin-top: 0.8em;
margin-left: 1em !important;
`;
const FlexFullHeight = styled.div`
flex-direction: column;
justify-content: space-around;
align-self: stretch;
`;
const FooterWrapper = styled.div`
padding-bottom: 1rem;
`;
const ContentLeft = styled.div`
margin-bottom: 0 !important;
overflow: hidden;
`;
const ContentRight = styled.div`
margin-left: auto;
`;
export default class CardColumn extends React.Component<Props> {
createLink = () => {
const { link, action } = this.props;
if (link) {
return <Link className="overlay-column" to={link} />;
} else if (action) {
return <a className="overlay-column" onClick={e => {e.preventDefault(); action();}} href="#" />;
return (
<a
className="overlay-column"
onClick={e => {
e.preventDefault();
action();
}}
href="#"
/>
);
}
return null;
};
@@ -74,49 +73,37 @@ class CardColumn extends React.Component<Props> {
contentRight,
footerLeft,
footerRight,
classes,
className
} = this.props;
const link = this.createLink();
return (
<>
{link}
<article className={classNames("media", className, classes.inner)}>
<figure className={classNames(classes.centerImage, "media-left")}>
{avatar}
</figure>
<div
className={classNames(
"media-content",
"text-box",
classes.flexFullHeight
)}
<NoEventWrapper
className={classNames("media", "is-relative", className)}
>
<AvatarWrapper className="media-left">{avatar}</AvatarWrapper>
<FlexFullHeight
className={classNames("media-content", "text-box", "is-flex")}
>
<div className={classes.topPart}>
<div
className={classNames(
"content",
classes.contentLeft
)}
>
<div className="is-flex">
<ContentLeft className="content">
<p className="shorten-text is-marginless">
<strong>{title}</strong>
</p>
<p className="shorten-text">{description}</p>
</div>
<div className={classes.contentRight}>
{contentRight && contentRight}
</div>
</ContentLeft>
<ContentRight>{contentRight && contentRight}</ContentRight>
</div>
<div className={classNames("level", classes.footer)}>
<div className="level-left is-hidden-mobile">{footerLeft}</div>
<div className="level-right is-mobile is-marginless">{footerRight}</div>
<FooterWrapper className={classNames("level", "is-flex")}>
<div className="level-left is-hidden-mobile">{footerLeft}</div>
<div className="level-right is-mobile is-marginless">
{footerRight}
</div>
</div>
</article>
</FooterWrapper>
</FlexFullHeight>
</NoEventWrapper>
</>
);
}
}
export default injectSheet(styles)(CardColumn);

View File

@@ -1,37 +1,26 @@
//@flow
import * as React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
const styles = {
pointer: {
cursor: "pointer",
fontSize: "1.5rem"
},
repoGroup: {
marginBottom: "1em"
},
wrapper: {
padding: "0 0.75rem"
},
clearfix: {
clear: "both"
}
};
import styled from "styled-components";
type Props = {
name: string,
elements: React.Node[],
// context props
classes: any
elements: React.Node[]
};
type State = {
collapsed: boolean
};
class CardColumnGroup extends React.Component<Props, State> {
const Container = styled.div`
margin-bottom: 1em;
`;
const Wrapper = styled.div`
padding: 0 0.75rem;
`;
export default class CardColumnGroup extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
@@ -58,7 +47,7 @@ class CardColumnGroup extends React.Component<Props, State> {
};
render() {
const { name, elements, classes } = this.props;
const { name, elements } = this.props;
const { collapsed } = this.state;
const icon = collapsed ? "fa-angle-right" : "fa-angle-down";
@@ -84,20 +73,20 @@ class CardColumnGroup extends React.Component<Props, State> {
});
}
return (
<div className={classes.repoGroup}>
<Container className={classNames("is-clearfix")}>
<h2>
<span className={classes.pointer} onClick={this.toggleCollapse}>
<span
className={classNames("is-size-4", "has-cursor-pointer")}
onClick={this.toggleCollapse}
>
<i className={classNames("fa", icon)} /> {name}
</span>
</h2>
<hr />
<div className={classNames("columns", "is-multiline", classes.wrapper)}>
<Wrapper className={classNames("columns", "is-multiline")}>
{content}
</div>
<div className={classes.clearfix} />
</div>
</Wrapper>
</Container>
);
}
}
export default injectSheet(styles)(CardColumnGroup);

View File

@@ -2,39 +2,35 @@
import React from "react";
import moment from "moment";
import { translate } from "react-i18next";
import injectSheet from "react-jss";
import styled from "styled-components";
// fix german locale
// https://momentjscom.readthedocs.io/en/latest/moment/00-use-it/07-browserify/
import "moment/locale/de";
const styles = {
date: {
borderBottom: "1px dotted rgba(219, 219, 219)",
cursor: "help"
}
};
type Props = {
date?: string,
// context props
classes: any,
i18n: any
};
class DateFromNow extends React.Component<Props> {
const Date = styled.time`
border-bottom: 1px dotted rgba(219, 219, 219);
cursor: help;
`;
class DateFromNow extends React.Component<Props> {
render() {
const { i18n, date, classes } = this.props;
const { i18n, date } = this.props;
if (date) {
const dateWithLocale = moment(date).locale(i18n.language);
return (
<time title={dateWithLocale.format()} className={classes.date}>
<Date title={dateWithLocale.format()}>
{dateWithLocale.fromNow()}
</time>
</Date>
);
}
@@ -42,4 +38,4 @@ class DateFromNow extends React.Component<Props> {
}
}
export default injectSheet(styles)(translate()(DateFromNow));
export default translate()(DateFromNow);

View File

@@ -1,36 +1,27 @@
//@flow
import React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
import styled from "styled-components";
import Tooltip from "./Tooltip";
import HelpIcon from "./HelpIcon";
const styles = {
tooltip: {
display: "inline-block",
paddingLeft: "3px",
position: "absolute"
}
};
type Props = {
message: string,
className?: string,
classes: any
className?: string
};
class Help extends React.Component<Props> {
const HelpTooltip = styled(Tooltip)`
position: absolute;
padding-left: 3px;
`;
export default class Help extends React.Component<Props> {
render() {
const { message, className, classes } = this.props;
const { message, className } = this.props;
return (
<Tooltip
className={classNames(classes.tooltip, className)}
message={message}
>
<HelpTooltip className={classNames("is-inline-block", className)} message={message}>
<HelpIcon />
</Tooltip>
</HelpTooltip>
);
}
}
export default injectSheet(styles)(Help);

View File

@@ -1,25 +1,16 @@
//@flow
import React from "react";
import injectSheet from "react-jss";
import Icon from "./Icon";
type Props = {
classes: any
className?: string
};
const styles = {
textinfo: {
color: "#98d8f3 !important"
}
};
class HelpIcon extends React.Component<Props> {
export default class HelpIcon extends React.Component<Props> {
render() {
const { classes } = this.props;
const { className } = this.props;
return (
<Icon className={classes.textinfo} name="question-circle" />
<Icon name="question-circle" color="blue-light" className={className} />
);
}
}
export default injectSheet(styles)(HelpIcon);

View File

@@ -1,57 +1,35 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import injectSheet from "react-jss";
import styled from "styled-components";
import Image from "./Image";
const styles = {
minHeightContainer: {
minHeight: "256px"
},
wrapper: {
position: "relative"
},
loading: {
width: "128px",
height: "128px",
position: "absolute",
top: "50%",
left: "50%",
margin: "64px 0 0 -64px"
},
image: {
width: "128px",
height: "128px"
}
};
type Props = {
t: string => string,
message?: string,
classes: any
message?: string
};
const Wrapper = styled.div`
align-items: center;
justify-content: center;
min-height: 256px;
`;
const FixedSizedImage = styled(Image)`
width: 128px;
height: 128px;
`;
class Loading extends React.Component<Props> {
render() {
const { message, t, classes } = this.props;
const { message, t } = this.props;
return (
<div className={classes.minHeightContainer}>
<div className={classes.wrapper}>
<div className={classes.loading}>
<Image
className={classes.image}
src="/images/loading.svg"
alt={t("loading.alt")}
/>
<p className="has-text-centered">{message}</p>
</div>
</div>
</div>
<Wrapper className="is-flex">
<FixedSizedImage src="/images/loading.svg" alt={t("loading.alt")} />
<p className="has-text-centered">{message}</p>
</Wrapper>
);
}
}
export default injectSheet(styles)(translate("commons")(Loading));
export default translate("commons")(Loading);

View File

@@ -1,8 +1,8 @@
//@flow
import React from "react";
import { withRouter } from "react-router-dom";
import injectSheet from "react-jss";
import Markdown from "react-markdown/with-html";
import styled from "styled-components";
import { binder } from "@scm-manager/ui-extensions";
import SyntaxHighlighter from "./SyntaxHighlighter";
import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer";
@@ -14,32 +14,29 @@ type Props = {
enableAnchorHeadings: boolean,
// context props
classes: any,
location: any
};
const styles = {
markdown: {
"& > .content": {
"& > h1, h2, h3, h4, h5, h6": {
margin: "0.5rem 0",
fontSize: "0.9rem"
},
"& > h1": {
fontWeight: "700"
},
"& > h2": {
fontWeight: "600"
},
"& > h3, h4, h5, h6": {
fontWeight: "500"
},
"& strong": {
fontWeight: "500"
}
const MarkdownWrapper = styled.div`
> .content: {
> h1, h2, h3, h4, h5, h6: {
margin: 0.5rem 0;
font-size: 0.9rem;
}
> h1: {
font-weight: 700;
}
> h2: {
font-weight: 600;
}
> h3, h4, h5, h6: {
font-weight: 500;
}
& strong: {
font-weight: 500;
}
}
};
`;
class MarkdownView extends React.Component<Props> {
static defaultProps = {
@@ -72,8 +69,7 @@ class MarkdownView extends React.Component<Props> {
content,
renderers,
renderContext,
enableAnchorHeadings,
classes
enableAnchorHeadings
} = this.props;
const rendererFactory = binder.getExtension("markdown-renderer-factory");
@@ -96,7 +92,7 @@ class MarkdownView extends React.Component<Props> {
}
return (
<div className={classes.markdown} ref={el => (this.contentRef = el)}>
<MarkdownWrapper ref={el => (this.contentRef = el)}>
<Markdown
className="content"
skipHtml={true}
@@ -104,9 +100,9 @@ class MarkdownView extends React.Component<Props> {
source={content}
renderers={rendererList}
/>
</div>
</MarkdownWrapper>
);
}
}
export default injectSheet(styles)(withRouter(MarkdownView));
export default withRouter(MarkdownView);

View File

@@ -1,11 +1,10 @@
// @flow
//@flow
import React from "react";
import type { History } from "history";
import { withRouter } from "react-router-dom";
import classNames from "classnames";
import injectSheet from "react-jss";
import { FilterInput } from "./forms";
import { Button, urls } from "./index";
import { FilterInput } from "./forms";
type Props = {
showCreateButton: boolean,
@@ -13,19 +12,10 @@ type Props = {
label?: string,
// context props
classes: Object,
history: History,
location: any
};
const styles = {
button: {
float: "right",
marginTop: "1.25rem",
marginLeft: "1.25rem"
}
};
class OverviewPageActions extends React.Component<Props> {
render() {
const { history, location, link } = this.props;
@@ -43,10 +33,10 @@ class OverviewPageActions extends React.Component<Props> {
}
renderCreateButton() {
const { showCreateButton, classes, link, label } = this.props;
const { showCreateButton, link, label } = this.props;
if (showCreateButton) {
return (
<div className={classNames(classes.button, "input-button control")}>
<div className={classNames("input-button", "control")}>
<Button label={label} link={`/${link}/create`} color="primary" />
</div>
);
@@ -55,4 +45,4 @@ class OverviewPageActions extends React.Component<Props> {
}
}
export default injectSheet(styles)(withRouter(OverviewPageActions));
export default withRouter(OverviewPageActions);

View File

@@ -14,10 +14,7 @@ export type ButtonProps = {
icon?: string,
fullWidth?: boolean,
reducedMobile?: boolean,
children?: React.Node,
// context props
classes: any
children?: React.Node
};
type Props = ButtonProps & {

View File

@@ -1,28 +1,20 @@
//@flow
import React from "react";
import injectSheet from "react-jss";
import { type ButtonProps } from "./Button";
import classNames from "classnames";
import Button from "./Button";
import styled from "styled-components";
import Button, { type ButtonProps } from "./Button";
const styles = {
spacing: {
marginTop: "2em",
border: "2px solid #e9f7fd",
padding: "1em 1em"
}
};
const Wrapper = styled.div`
margin-top: 2em;
padding: 1em 1em;
border: 2px solid #e9f7fd;
`;
class CreateButton extends React.Component<ButtonProps> {
export default class CreateButton extends React.Component<ButtonProps> {
render() {
const { classes } = this.props;
return (
<div className={classNames("has-text-centered", classes.spacing)}>
<Wrapper className="has-text-centered">
<Button color="primary" {...this.props} />
</div>
</Wrapper>
);
}
}
export default injectSheet(styles)(CreateButton);

View File

@@ -1,15 +1,13 @@
//@flow
import React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
import { translate } from "react-i18next";
import styled from "styled-components";
type Props = {
filter: string => void,
value?: string,
// context props
classes: Object,
t: string => string
};
@@ -17,15 +15,9 @@ type State = {
value: string
};
const styles = {
inputField: {
float: "right",
marginTop: "1.25rem"
},
inputHeight: {
height: "2.5rem"
}
};
const FixedHeightInput = styled.input`
height: 2.5rem;
`;
class FilterInput extends React.Component<Props, State> {
constructor(props) {
@@ -43,15 +35,12 @@ class FilterInput extends React.Component<Props, State> {
};
render() {
const { classes, t } = this.props;
const { t } = this.props;
return (
<form
className={classNames(classes.inputField, "input-field")}
onSubmit={this.handleSubmit}
>
<form className="input-field" onSubmit={this.handleSubmit}>
<div className="control has-icons-left">
<input
className={classNames(classes.inputHeight, "input")}
<FixedHeightInput
className="input"
type="search"
placeholder={t("filterEntries")}
value={this.state.value}
@@ -66,4 +55,4 @@ class FilterInput extends React.Component<Props, State> {
}
}
export default injectSheet(styles)(translate("commons")(FilterInput));
export default translate("commons")(FilterInput);

View File

@@ -1,6 +1,5 @@
//@flow
import * as React from "react";
import injectSheet from "react-jss";
import type { DisplayedUser } from "@scm-manager/ui-types";
import { Help, Tag } from "../index";
@@ -8,24 +7,15 @@ type Props = {
items: DisplayedUser[],
label: string,
helpText?: string,
onRemove: (DisplayedUser[]) => void,
// context props
classes: Object
onRemove: (DisplayedUser[]) => void
};
const styles = {
help: {
position: "relative"
}
};
class TagGroup extends React.Component<Props> {
export default class TagGroup extends React.Component<Props> {
render() {
const { items, label, helpText, classes } = this.props;
const { items, label, helpText } = this.props;
let help = null;
if (helpText) {
help = <Help className={classes.help} message={helpText} />;
help = <Help className="is-relative" message={helpText} />;
}
return (
<div className="field is-grouped is-grouped-multiline">
@@ -62,5 +52,3 @@ class TagGroup extends React.Component<Props> {
this.props.onRemove(newItems);
};
}
export default injectSheet(styles)(TagGroup);

View File

@@ -1,7 +1,7 @@
//@flow
import * as React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
import styled from "styled-components";
import Loading from "./../Loading";
import ErrorNotification from "./../ErrorNotification";
import Title from "./Title";
@@ -15,20 +15,20 @@ type Props = {
loading?: boolean,
error?: Error,
showContentOnError?: boolean,
children: React.Node,
// context props
classes: Object
children: React.Node
};
const styles = {
actions: {
display: "flex",
justifyContent: "flex-end"
const PageActionContainer = styled.div`
justify-content: flex-end;
align-items: center;
// every child except first
> * ~ * {
margin-left: 1.25rem;
}
};
`;
class Page extends React.Component<Props> {
export default class Page extends React.Component<Props> {
render() {
const { error } = this.props;
return (
@@ -45,12 +45,14 @@ class Page extends React.Component<Props> {
}
isPageAction(node: any) {
return node.displayName === PageActions.displayName
|| (node.type && node.type.displayName === PageActions.displayName);
return (
node.displayName === PageActions.displayName ||
(node.type && node.type.displayName === PageActions.displayName)
);
}
renderPageHeader() {
const { error, title, subtitle, children, classes } = this.props;
const { error, title, subtitle, children } = this.props;
let pageActions = null;
let pageActionsExists = false;
@@ -58,14 +60,16 @@ class Page extends React.Component<Props> {
if (child && !error) {
if (this.isPageAction(child)) {
pageActions = (
<div
<PageActionContainer
className={classNames(
classes.actions,
"column is-three-fifths is-mobile-action-spacing"
"column",
"is-three-fifths",
"is-mobile-action-spacing",
"is-flex"
)}
>
{child}
</div>
</PageActionContainer>
);
pageActionsExists = true;
}
@@ -110,5 +114,3 @@ class Page extends React.Component<Props> {
return content;
}
}
export default injectSheet(styles)(Page);

View File

@@ -1,5 +1,8 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import {
Change,
Diff as DiffComponent,
@@ -8,71 +11,14 @@ import {
getChangeKey,
Hunk
} from "react-diff-view";
import injectSheets from "react-jss";
import classNames from "classnames";
import { translate } from "react-i18next";
import { Button, ButtonGroup } from "../buttons";
import Tag from "../Tag";
const styles = {
panel: {
fontSize: "1rem"
},
/* breaks into a second row
when buttons and title become too long */
level: {
flexWrap: "wrap"
},
titleHeader: {
display: "flex",
maxWidth: "100%",
cursor: "pointer"
},
title: {
marginLeft: ".25rem",
fontSize: "1rem"
},
/* align child to right */
buttonHeader: {
display: "flex",
marginLeft: "auto"
},
hunkDivider: {
margin: ".5rem 0"
},
changeType: {
marginLeft: ".75rem"
},
diff: {
/* column sizing */
"& > colgroup .diff-gutter-col": {
width: "3.25rem"
},
/* prevent following content from moving down */
"& > .diff-gutter:empty:hover::after": {
fontSize: "0.7rem"
},
/* smaller font size for code */
"& .diff-line": {
fontSize: "0.75rem"
},
/* comment padding for sideBySide view */
"&.split .diff-widget-content .is-indented-line": {
paddingLeft: "3.25rem"
},
/* comment padding for combined view */
"&.unified .diff-widget-content .is-indented-line": {
paddingLeft: "6.5rem"
}
}
};
type Props = DiffObjectProps & {
file: File,
collapsible: true,
// context props
classes: any,
t: string => string
};
@@ -81,6 +27,64 @@ type State = {
sideBySide: boolean
};
const DiffFilePanel = styled.div`
${props =>
props.file &&
props.file.isBinary && {
borderBottom: "none"
}};
`;
const FlexWrapLevel = styled.div`
/* breaks into a second row
when buttons and title become too long */
flex-wrap: wrap;
`;
const FullWidthTitleHeader = styled.div`
max-width: 100%;
`;
const TitleWrapper = styled.span`
margin-left: 0.25rem;
`;
const ButtonWrapper = styled.div`
/* align child to right */
margin-left: auto;
`;
const HunkDivider = styled.hr`
margin: 0.5rem 0;
`;
const ChangeTypeTag = styled(Tag)`
marginleft: ".75rem";
`;
const ModifiedDiffComponent = styled(DiffComponent)`
/* column sizing */
> colgroup .diff-gutter-col: {
width: 3.25rem;
}
/* prevent following content from moving down */
> .diff-gutter:empty:hover::after: {
font-size: 0.7rem;
}
/* smaller font size for code */
& .diff-line: {
font-size: 0.75rem;
}
/* comment padding for sidebyside view */
&.split .diff-widget-content .is-indented-line: {
padding-left: 3.25rem;
}
/* comment padding for combined view */
&.unified .diff-widget-content .is-indented-line: {
padding-left: 6.5rem;
}
`;
class DiffFile extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -111,9 +115,8 @@ class DiffFile extends React.Component<Props, State> {
};
createHunkHeader = (hunk: Hunk, i: number) => {
const { classes } = this.props;
if (i > 0) {
return <hr className={classes.hunkDivider} />;
return <HunkDivider />;
}
return null;
};
@@ -199,7 +202,7 @@ class DiffFile extends React.Component<Props, State> {
};
renderChangeTag = (file: any) => {
const { t, classes } = this.props;
const { t } = this.props;
if (!file.type) {
return;
}
@@ -216,12 +219,8 @@ class DiffFile extends React.Component<Props, State> {
: "info is-outlined";
return (
<Tag
className={classNames(
"is-rounded",
"has-text-weight-normal",
classes.changeType
)}
<ChangeTypeTag
className={classNames("is-rounded", "has-text-weight-normal")}
color={color}
label={value}
/>
@@ -234,7 +233,6 @@ class DiffFile extends React.Component<Props, State> {
fileControlFactory,
fileAnnotationFactory,
collapsible,
classes,
t
} = this.props;
const { collapsed, sideBySide } = this.state;
@@ -250,12 +248,9 @@ class DiffFile extends React.Component<Props, State> {
body = (
<div className="panel-block is-paddingless">
{fileAnnotations}
<DiffComponent
className={classNames(viewType, classes.diff)}
viewType={viewType}
>
<ModifiedDiffComponent className={viewType} viewType={viewType}>
{file.hunks.map(this.renderHunk)}
</DiffComponent>
</ModifiedDiffComponent>
</div>
);
}
@@ -265,23 +260,27 @@ class DiffFile extends React.Component<Props, State> {
? fileControlFactory(file, this.setCollapse)
: null;
return (
<div className={classNames("panel", classes.panel)}>
<DiffFilePanel className={classNames("panel", "is-size-6")}>
<div className="panel-heading">
<div className={classNames("level", classes.level)}>
<div
className={classNames("level-left", classes.titleHeader)}
<FlexWrapLevel className="level">
<FullWidthTitleHeader
className={classNames(
"level-left",
"is-flex",
"has-cursor-pointer"
)}
onClick={this.toggleCollapse}
title={this.hoverFileTitle(file)}
>
{collapseIcon}
<span
className={classNames("is-ellipsis-overflow", classes.title)}
<TitleWrapper
className={classNames("is-ellipsis-overflow", "is-size-6")}
>
{this.renderFileTitle(file)}
</span>
</TitleWrapper>
{this.renderChangeTag(file)}
</div>
<div className={classNames("level-right", classes.buttonHeader)}>
</FullWidthTitleHeader>
<ButtonWrapper className={classNames("level-right", "is-flex")}>
<ButtonGroup>
<Button
action={this.toggleSideBySide}
@@ -301,13 +300,13 @@ class DiffFile extends React.Component<Props, State> {
</Button>
{fileControls}
</ButtonGroup>
</div>
</div>
</ButtonWrapper>
</FlexWrapLevel>
</div>
{body}
</div>
</DiffFilePanel>
);
}
}
export default injectSheets(styles)(translate("repos")(DiffFile));
export default translate("repos")(DiffFile);

View File

@@ -1,60 +1,63 @@
//@flow
import React from "react";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import classNames from "classnames";
import { Interpolate, translate } from "react-i18next";
import ChangesetId from "./ChangesetId";
import injectSheet from "react-jss";
import { DateFromNow } from "../..";
import ChangesetAuthor from "./ChangesetAuthor";
import { parseDescription } from "./changesets";
import { AvatarWrapper, AvatarImage } from "../../avatar";
import classNames from "classnames";
import styled from "styled-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import DateFromNow from "../../DateFromNow";
import { AvatarWrapper, AvatarImage } from "../../avatar";
import { parseDescription } from "./changesets";
import ChangesetId from "./ChangesetId";
import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTags from "./ChangesetTags";
import ChangesetButtonGroup from "./ChangesetButtonGroup";
const styles = {
changeset: {
// & references parent rule
// have a look at https://cssinjs.org/jss-plugin-nested?v=v10.0.0-alpha.9
"& + &": {
borderTop: "1px solid rgba(219, 219, 219, 0.5)",
marginTop: "1rem",
paddingTop: "1rem"
}
},
avatarFigure: {
marginTop: ".5rem",
marginRight: ".5rem"
},
avatarImage: {
height: "35px",
width: "35px"
},
metadata: {
marginLeft: 0
},
authorMargin: {
marginTop: "0.5rem"
},
isVcentered: {
alignSelf: "center"
},
flexVcenter: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end"
}
};
type Props = {
repository: Repository,
changeset: Changeset,
t: any,
classes: any
// context props
t: string => string
};
const Wrapper = styled.div`
// & references parent rule
// have a look at https://cssinjs.org/jss-plugin-nested?v=v10.0.0-alpha.9
& + &: {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid rgba(219, 219, 219, 0.5);
}
`;
const AvatarFigure = styled.figure`
margin-top: 0.5rem;
margin-right: 0.5rem;
`;
const FixedSizedAvatar = styled.div`
width: 35px;
height: 35px;
`;
const Metadata = styled.div`
margin-left: 0;
`;
const AuthorWrapper = styled.p`
margin-top: 0.5rem;
`;
const VCenteredColumn = styled.div`
align-self: center;
`;
const VCenteredChildColumn = styled.div`
align-items: center;
justify-content: flex-end;
`;
class ChangesetRow extends React.Component<Props> {
createChangesetId = (changeset: Changeset) => {
const { repository } = this.props;
@@ -62,28 +65,26 @@ class ChangesetRow extends React.Component<Props> {
};
render() {
const { repository, changeset, classes } = this.props;
const { repository, changeset } = this.props;
const description = parseDescription(changeset.description);
const changesetId = this.createChangesetId(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />;
return (
<div className={classes.changeset}>
<Wrapper>
<div className="columns is-gapless is-mobile">
<div className="column is-three-fifths">
<div className="columns is-gapless">
<div className="column is-four-fifths">
<div className="media">
<AvatarWrapper>
<figure
className={classNames(classes.avatarFigure, "media-left")}
>
<div className={classNames("image", classes.avatarImage)}>
<AvatarFigure className="media-left">
<FixedSizedAvatar className="image">
<AvatarImage person={changeset.author} />
</div>
</figure>
</FixedSizedAvatar>
</AvatarFigure>
</AvatarWrapper>
<div className={classNames(classes.metadata, "media-right")}>
<Metadata className="media-right">
<h4 className="has-text-weight-bold is-ellipsis-overflow">
<ExtensionPoint
name="changeset.description"
@@ -107,18 +108,18 @@ class ChangesetRow extends React.Component<Props> {
time={dateFromNow}
/>
</p>
<p className={classNames("is-size-7", classes.authorMargin)}>
<AuthorWrapper className="is-size-7">
<ChangesetAuthor changeset={changeset} />
</p>
</div>
</AuthorWrapper>
</Metadata>
</div>
</div>
<div className={classNames("column", classes.isVcentered)}>
<VCenteredColumn className="column">
<ChangesetTags changeset={changeset} />
</div>
</VCenteredColumn>
</div>
</div>
<div className={classNames("column", classes.flexVcenter)}>
<VCenteredChildColumn className={classNames("column", "is-flex")}>
<ChangesetButtonGroup
repository={repository}
changeset={changeset}
@@ -128,11 +129,11 @@ class ChangesetRow extends React.Component<Props> {
props={{ repository, changeset }}
renderAll={true}
/>
</div>
</VCenteredChildColumn>
</div>
</div>
</Wrapper>
);
}
}
export default injectSheet(styles)(translate("repos")(ChangesetRow));
export default translate("repos")(ChangesetRow);