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,10 +1,9 @@
//@flow //@flow
import React from "react"; import React from "react";
import { ButtonAddons, Button } from "@scm-manager/ui-components";
import type { Repository } from "@scm-manager/ui-types";
import CloneInformation from "./CloneInformation";
import type { Link } from "@scm-manager/ui-types";
import styled from "styled-components"; import styled from "styled-components";
import type { Repository, Link } from "@scm-manager/ui-types";
import { ButtonAddons, Button } from "@scm-manager/ui-components";
import CloneInformation from "./CloneInformation";
const Wrapper = styled.div` const Wrapper = styled.div`
position: relative; position: relative;
@@ -18,7 +17,7 @@ const Switcher = styled(ButtonAddons)`
type Props = { type Props = {
repository: Repository repository: Repository
} };
type State = { type State = {
selected?: Link selected?: Link
@@ -39,8 +38,7 @@ function selectHttpOrFirst(repository: Repository) {
return undefined; return undefined;
} }
class ProtocolInformation extends React.Component<Props, State> { export default class ProtocolInformation extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
@@ -60,12 +58,12 @@ class ProtocolInformation extends React.Component<Props, State> {
let color = null; let color = null;
const { selected } = this.state; const { selected } = this.state;
if ( selected && protocol.name === selected.name ) { if (selected && protocol.name === selected.name) {
color = "link is-selected"; color = "link is-selected";
} }
return ( return (
<Button color={ color } action={() => this.selectProtocol(protocol)}> <Button color={color} action={() => this.selectProtocol(protocol)}>
{name.toUpperCase()} {name.toUpperCase()}
</Button> </Button>
); );
@@ -80,25 +78,24 @@ class ProtocolInformation extends React.Component<Props, State> {
} }
if (protocols.length === 1) { if (protocols.length === 1) {
return <CloneInformation url={protocols[0].href} repository={repository} />; return (
<CloneInformation url={protocols[0].href} repository={repository} />
);
} }
const { selected } = this.state; const { selected } = this.state;
let cloneInformation = null; let cloneInformation = null;
if (selected) { if (selected) {
cloneInformation = <CloneInformation repository={repository} url={selected.href} />; cloneInformation = (
<CloneInformation repository={repository} url={selected.href} />
);
} }
return ( return (
<Wrapper> <Wrapper>
<Switcher> <Switcher>{protocols.map(this.renderProtocolButton)}</Switcher>
{protocols.map(this.renderProtocolButton)} {cloneInformation}
</Switcher> </Wrapper>
{ cloneInformation }
</Wrapper>
); );
} }
} }
export default ProtocolInformation;

View File

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

View File

@@ -2,8 +2,8 @@
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import injectSheet from "react-jss";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components";
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import type { Branch, Repository } from "@scm-manager/ui-types"; import type { Branch, Repository } from "@scm-manager/ui-types";
import Icon from "./Icon"; import Icon from "./Icon";
@@ -18,29 +18,21 @@ type Props = {
baseUrl: string, baseUrl: string,
// Context props // Context props
classes: any,
t: string => string t: string => string
}; };
const styles = { const FlexStartNav = styled.nav`
noMargin: { flex: 1;
margin: "0" `;
},
flexRow: { const HomeIcon = styled(Icon)`
display: "flex", line-height: 1.5rem;
flexDirection: "row" `;
},
flexStart: { const ActionWrapper = styled.div`
flex: "1" align-self: center;
}, padding-right: 1rem;
homeIcon: { `;
lineHeight: "1.5rem"
},
buttonGroup: {
alignSelf: "center",
paddingRight: "1rem"
}
};
class Breadcrumb extends React.Component<Props> { class Breadcrumb extends React.Component<Props> {
renderPath() { renderPath() {
@@ -79,25 +71,20 @@ class Breadcrumb extends React.Component<Props> {
revision, revision,
path, path,
repository, repository,
classes,
t t
} = this.props; } = this.props;
return ( return (
<> <>
<div className={classes.flexRow}> <div className="is-flex">
<nav <FlexStartNav
className={classNames( className={classNames("breadcrumb", "sources-breadcrumb")}
classes.flexStart,
"breadcrumb sources-breadcrumb"
)}
aria-label="breadcrumbs" aria-label="breadcrumbs"
> >
<ul> <ul>
<li> <li>
<Link to={baseUrl + "/" + revision + "/"}> <Link to={baseUrl + "/" + revision + "/"}>
<Icon <HomeIcon
className={classes.homeIcon}
title={t("breadcrumb.home")} title={t("breadcrumb.home")}
name="home" name="home"
color="inherit" color="inherit"
@@ -106,9 +93,9 @@ class Breadcrumb extends React.Component<Props> {
</li> </li>
{this.renderPath()} {this.renderPath()}
</ul> </ul>
</nav> </FlexStartNav>
{binder.hasExtension("repos.sources.actionbar") && ( {binder.hasExtension("repos.sources.actionbar") && (
<div className={classes.buttonGroup}> <ActionWrapper>
<ExtensionPoint <ExtensionPoint
name="repos.sources.actionbar" name="repos.sources.actionbar"
props={{ props={{
@@ -124,13 +111,13 @@ class Breadcrumb extends React.Component<Props> {
}} }}
renderAll={true} renderAll={true}
/> />
</div> </ActionWrapper>
)} )}
</div> </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 //@flow
import * as React from "react"; import * as React from "react";
import injectSheet from "react-jss";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components";
import { Link } from "react-router-dom"; 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 = { type Props = {
title: string, title: string,
description: string, description: string,
@@ -49,19 +13,54 @@ type Props = {
footerRight: React.Node, footerRight: React.Node,
link?: string, link?: string,
action?: () => void, action?: () => void,
className?: string, className?: string
// context props
classes: any
}; };
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 = () => { createLink = () => {
const { link, action } = this.props; const { link, action } = this.props;
if (link) { if (link) {
return <Link className="overlay-column" to={link} />; return <Link className="overlay-column" to={link} />;
} else if (action) { } 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; return null;
}; };
@@ -74,49 +73,37 @@ class CardColumn extends React.Component<Props> {
contentRight, contentRight,
footerLeft, footerLeft,
footerRight, footerRight,
classes,
className className
} = this.props; } = this.props;
const link = this.createLink(); const link = this.createLink();
return ( return (
<> <>
{link} {link}
<article className={classNames("media", className, classes.inner)}> <NoEventWrapper
<figure className={classNames(classes.centerImage, "media-left")}> className={classNames("media", "is-relative", className)}
{avatar} >
</figure> <AvatarWrapper className="media-left">{avatar}</AvatarWrapper>
<div <FlexFullHeight
className={classNames( className={classNames("media-content", "text-box", "is-flex")}
"media-content",
"text-box",
classes.flexFullHeight
)}
> >
<div className={classes.topPart}> <div className="is-flex">
<div <ContentLeft className="content">
className={classNames(
"content",
classes.contentLeft
)}
>
<p className="shorten-text is-marginless"> <p className="shorten-text is-marginless">
<strong>{title}</strong> <strong>{title}</strong>
</p> </p>
<p className="shorten-text">{description}</p> <p className="shorten-text">{description}</p>
</div> </ContentLeft>
<div className={classes.contentRight}> <ContentRight>{contentRight && contentRight}</ContentRight>
{contentRight && contentRight}
</div>
</div> </div>
<div className={classNames("level", classes.footer)}> <FooterWrapper className={classNames("level", "is-flex")}>
<div className="level-left is-hidden-mobile">{footerLeft}</div> <div className="level-left is-hidden-mobile">{footerLeft}</div>
<div className="level-right is-mobile is-marginless">{footerRight}</div> <div className="level-right is-mobile is-marginless">
{footerRight}
</div> </div>
</div> </FooterWrapper>
</article> </FlexFullHeight>
</NoEventWrapper>
</> </>
); );
} }
} }
export default injectSheet(styles)(CardColumn);

View File

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

View File

@@ -2,39 +2,35 @@
import React from "react"; import React from "react";
import moment from "moment"; import moment from "moment";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import injectSheet from "react-jss"; import styled from "styled-components";
// fix german locale // fix german locale
// https://momentjscom.readthedocs.io/en/latest/moment/00-use-it/07-browserify/ // https://momentjscom.readthedocs.io/en/latest/moment/00-use-it/07-browserify/
import "moment/locale/de"; import "moment/locale/de";
const styles = {
date: {
borderBottom: "1px dotted rgba(219, 219, 219)",
cursor: "help"
}
};
type Props = { type Props = {
date?: string, date?: string,
// context props // context props
classes: any,
i18n: 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() { render() {
const { i18n, date, classes } = this.props; const { i18n, date } = this.props;
if (date) { if (date) {
const dateWithLocale = moment(date).locale(i18n.language); const dateWithLocale = moment(date).locale(i18n.language);
return ( return (
<time title={dateWithLocale.format()} className={classes.date}> <Date title={dateWithLocale.format()}>
{dateWithLocale.fromNow()} {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 //@flow
import React from "react"; import React from "react";
import injectSheet from "react-jss";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components";
import Tooltip from "./Tooltip"; import Tooltip from "./Tooltip";
import HelpIcon from "./HelpIcon"; import HelpIcon from "./HelpIcon";
const styles = {
tooltip: {
display: "inline-block",
paddingLeft: "3px",
position: "absolute"
}
};
type Props = { type Props = {
message: string, message: string,
className?: string, className?: string
classes: any
}; };
class Help extends React.Component<Props> { const HelpTooltip = styled(Tooltip)`
position: absolute;
padding-left: 3px;
`;
export default class Help extends React.Component<Props> {
render() { render() {
const { message, className, classes } = this.props; const { message, className } = this.props;
return ( return (
<Tooltip <HelpTooltip className={classNames("is-inline-block", className)} message={message}>
className={classNames(classes.tooltip, className)}
message={message}
>
<HelpIcon /> <HelpIcon />
</Tooltip> </HelpTooltip>
); );
} }
} }
export default injectSheet(styles)(Help);

View File

@@ -1,25 +1,16 @@
//@flow //@flow
import React from "react"; import React from "react";
import injectSheet from "react-jss";
import Icon from "./Icon"; import Icon from "./Icon";
type Props = { type Props = {
classes: any className?: string
}; };
const styles = { export default class HelpIcon extends React.Component<Props> {
textinfo: {
color: "#98d8f3 !important"
}
};
class HelpIcon extends React.Component<Props> {
render() { render() {
const { classes } = this.props; const { className } = this.props;
return ( 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 //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import injectSheet from "react-jss"; import styled from "styled-components";
import Image from "./Image"; 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 = { type Props = {
t: string => string, t: string => string,
message?: string, message?: string
classes: any
}; };
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> { class Loading extends React.Component<Props> {
render() { render() {
const { message, t, classes } = this.props; const { message, t } = this.props;
return ( return (
<div className={classes.minHeightContainer}> <Wrapper className="is-flex">
<div className={classes.wrapper}> <FixedSizedImage src="/images/loading.svg" alt={t("loading.alt")} />
<div className={classes.loading}> <p className="has-text-centered">{message}</p>
<Image </Wrapper>
className={classes.image}
src="/images/loading.svg"
alt={t("loading.alt")}
/>
<p className="has-text-centered">{message}</p>
</div>
</div>
</div>
); );
} }
} }
export default injectSheet(styles)(translate("commons")(Loading)); export default translate("commons")(Loading);

View File

@@ -1,8 +1,8 @@
//@flow //@flow
import React from "react"; import React from "react";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import injectSheet from "react-jss";
import Markdown from "react-markdown/with-html"; import Markdown from "react-markdown/with-html";
import styled from "styled-components";
import { binder } from "@scm-manager/ui-extensions"; import { binder } from "@scm-manager/ui-extensions";
import SyntaxHighlighter from "./SyntaxHighlighter"; import SyntaxHighlighter from "./SyntaxHighlighter";
import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer"; import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer";
@@ -14,32 +14,29 @@ type Props = {
enableAnchorHeadings: boolean, enableAnchorHeadings: boolean,
// context props // context props
classes: any,
location: any location: any
}; };
const styles = { const MarkdownWrapper = styled.div`
markdown: { > .content: {
"& > .content": { > h1, h2, h3, h4, h5, h6: {
"& > h1, h2, h3, h4, h5, h6": { margin: 0.5rem 0;
margin: "0.5rem 0", font-size: 0.9rem;
fontSize: "0.9rem" }
}, > h1: {
"& > h1": { font-weight: 700;
fontWeight: "700" }
}, > h2: {
"& > h2": { font-weight: 600;
fontWeight: "600" }
}, > h3, h4, h5, h6: {
"& > h3, h4, h5, h6": { font-weight: 500;
fontWeight: "500" }
}, & strong: {
"& strong": { font-weight: 500;
fontWeight: "500"
}
} }
} }
}; `;
class MarkdownView extends React.Component<Props> { class MarkdownView extends React.Component<Props> {
static defaultProps = { static defaultProps = {
@@ -72,8 +69,7 @@ class MarkdownView extends React.Component<Props> {
content, content,
renderers, renderers,
renderContext, renderContext,
enableAnchorHeadings, enableAnchorHeadings
classes
} = this.props; } = this.props;
const rendererFactory = binder.getExtension("markdown-renderer-factory"); const rendererFactory = binder.getExtension("markdown-renderer-factory");
@@ -96,7 +92,7 @@ class MarkdownView extends React.Component<Props> {
} }
return ( return (
<div className={classes.markdown} ref={el => (this.contentRef = el)}> <MarkdownWrapper ref={el => (this.contentRef = el)}>
<Markdown <Markdown
className="content" className="content"
skipHtml={true} skipHtml={true}
@@ -104,9 +100,9 @@ class MarkdownView extends React.Component<Props> {
source={content} source={content}
renderers={rendererList} 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 React from "react";
import type { History } from "history"; import type { History } from "history";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import classNames from "classnames"; import classNames from "classnames";
import injectSheet from "react-jss";
import { FilterInput } from "./forms";
import { Button, urls } from "./index"; import { Button, urls } from "./index";
import { FilterInput } from "./forms";
type Props = { type Props = {
showCreateButton: boolean, showCreateButton: boolean,
@@ -13,19 +12,10 @@ type Props = {
label?: string, label?: string,
// context props // context props
classes: Object,
history: History, history: History,
location: any location: any
}; };
const styles = {
button: {
float: "right",
marginTop: "1.25rem",
marginLeft: "1.25rem"
}
};
class OverviewPageActions extends React.Component<Props> { class OverviewPageActions extends React.Component<Props> {
render() { render() {
const { history, location, link } = this.props; const { history, location, link } = this.props;
@@ -43,10 +33,10 @@ class OverviewPageActions extends React.Component<Props> {
} }
renderCreateButton() { renderCreateButton() {
const { showCreateButton, classes, link, label } = this.props; const { showCreateButton, link, label } = this.props;
if (showCreateButton) { if (showCreateButton) {
return ( return (
<div className={classNames(classes.button, "input-button control")}> <div className={classNames("input-button", "control")}>
<Button label={label} link={`/${link}/create`} color="primary" /> <Button label={label} link={`/${link}/create`} color="primary" />
</div> </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, icon?: string,
fullWidth?: boolean, fullWidth?: boolean,
reducedMobile?: boolean, reducedMobile?: boolean,
children?: React.Node, children?: React.Node
// context props
classes: any
}; };
type Props = ButtonProps & { type Props = ButtonProps & {

View File

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

View File

@@ -1,15 +1,13 @@
//@flow //@flow
import React from "react"; import React from "react";
import injectSheet from "react-jss";
import classNames from "classnames";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import styled from "styled-components";
type Props = { type Props = {
filter: string => void, filter: string => void,
value?: string, value?: string,
// context props // context props
classes: Object,
t: string => string t: string => string
}; };
@@ -17,15 +15,9 @@ type State = {
value: string value: string
}; };
const styles = { const FixedHeightInput = styled.input`
inputField: { height: 2.5rem;
float: "right", `;
marginTop: "1.25rem"
},
inputHeight: {
height: "2.5rem"
}
};
class FilterInput extends React.Component<Props, State> { class FilterInput extends React.Component<Props, State> {
constructor(props) { constructor(props) {
@@ -43,15 +35,12 @@ class FilterInput extends React.Component<Props, State> {
}; };
render() { render() {
const { classes, t } = this.props; const { t } = this.props;
return ( return (
<form <form className="input-field" onSubmit={this.handleSubmit}>
className={classNames(classes.inputField, "input-field")}
onSubmit={this.handleSubmit}
>
<div className="control has-icons-left"> <div className="control has-icons-left">
<input <FixedHeightInput
className={classNames(classes.inputHeight, "input")} className="input"
type="search" type="search"
placeholder={t("filterEntries")} placeholder={t("filterEntries")}
value={this.state.value} 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 //@flow
import * as React from "react"; import * as React from "react";
import injectSheet from "react-jss";
import type { DisplayedUser } from "@scm-manager/ui-types"; import type { DisplayedUser } from "@scm-manager/ui-types";
import { Help, Tag } from "../index"; import { Help, Tag } from "../index";
@@ -8,24 +7,15 @@ type Props = {
items: DisplayedUser[], items: DisplayedUser[],
label: string, label: string,
helpText?: string, helpText?: string,
onRemove: (DisplayedUser[]) => void, onRemove: (DisplayedUser[]) => void
// context props
classes: Object
}; };
const styles = { export default class TagGroup extends React.Component<Props> {
help: {
position: "relative"
}
};
class TagGroup extends React.Component<Props> {
render() { render() {
const { items, label, helpText, classes } = this.props; const { items, label, helpText } = this.props;
let help = null; let help = null;
if (helpText) { if (helpText) {
help = <Help className={classes.help} message={helpText} />; help = <Help className="is-relative" message={helpText} />;
} }
return ( return (
<div className="field is-grouped is-grouped-multiline"> <div className="field is-grouped is-grouped-multiline">
@@ -62,5 +52,3 @@ class TagGroup extends React.Component<Props> {
this.props.onRemove(newItems); this.props.onRemove(newItems);
}; };
} }
export default injectSheet(styles)(TagGroup);

View File

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

View File

@@ -1,5 +1,8 @@
//@flow //@flow
import React from "react"; import React from "react";
import { translate } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import { import {
Change, Change,
Diff as DiffComponent, Diff as DiffComponent,
@@ -8,71 +11,14 @@ import {
getChangeKey, getChangeKey,
Hunk Hunk
} from "react-diff-view"; } from "react-diff-view";
import injectSheets from "react-jss";
import classNames from "classnames";
import { translate } from "react-i18next";
import { Button, ButtonGroup } from "../buttons"; import { Button, ButtonGroup } from "../buttons";
import Tag from "../Tag"; 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 & { type Props = DiffObjectProps & {
file: File, file: File,
collapsible: true, collapsible: true,
// context props // context props
classes: any,
t: string => string t: string => string
}; };
@@ -81,6 +27,64 @@ type State = {
sideBySide: boolean 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> { class DiffFile extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@@ -111,9 +115,8 @@ class DiffFile extends React.Component<Props, State> {
}; };
createHunkHeader = (hunk: Hunk, i: number) => { createHunkHeader = (hunk: Hunk, i: number) => {
const { classes } = this.props;
if (i > 0) { if (i > 0) {
return <hr className={classes.hunkDivider} />; return <HunkDivider />;
} }
return null; return null;
}; };
@@ -199,7 +202,7 @@ class DiffFile extends React.Component<Props, State> {
}; };
renderChangeTag = (file: any) => { renderChangeTag = (file: any) => {
const { t, classes } = this.props; const { t } = this.props;
if (!file.type) { if (!file.type) {
return; return;
} }
@@ -216,12 +219,8 @@ class DiffFile extends React.Component<Props, State> {
: "info is-outlined"; : "info is-outlined";
return ( return (
<Tag <ChangeTypeTag
className={classNames( className={classNames("is-rounded", "has-text-weight-normal")}
"is-rounded",
"has-text-weight-normal",
classes.changeType
)}
color={color} color={color}
label={value} label={value}
/> />
@@ -234,7 +233,6 @@ class DiffFile extends React.Component<Props, State> {
fileControlFactory, fileControlFactory,
fileAnnotationFactory, fileAnnotationFactory,
collapsible, collapsible,
classes,
t t
} = this.props; } = this.props;
const { collapsed, sideBySide } = this.state; const { collapsed, sideBySide } = this.state;
@@ -250,12 +248,9 @@ class DiffFile extends React.Component<Props, State> {
body = ( body = (
<div className="panel-block is-paddingless"> <div className="panel-block is-paddingless">
{fileAnnotations} {fileAnnotations}
<DiffComponent <ModifiedDiffComponent className={viewType} viewType={viewType}>
className={classNames(viewType, classes.diff)}
viewType={viewType}
>
{file.hunks.map(this.renderHunk)} {file.hunks.map(this.renderHunk)}
</DiffComponent> </ModifiedDiffComponent>
</div> </div>
); );
} }
@@ -265,23 +260,27 @@ class DiffFile extends React.Component<Props, State> {
? fileControlFactory(file, this.setCollapse) ? fileControlFactory(file, this.setCollapse)
: null; : null;
return ( return (
<div className={classNames("panel", classes.panel)}> <DiffFilePanel className={classNames("panel", "is-size-6")}>
<div className="panel-heading"> <div className="panel-heading">
<div className={classNames("level", classes.level)}> <FlexWrapLevel className="level">
<div <FullWidthTitleHeader
className={classNames("level-left", classes.titleHeader)} className={classNames(
"level-left",
"is-flex",
"has-cursor-pointer"
)}
onClick={this.toggleCollapse} onClick={this.toggleCollapse}
title={this.hoverFileTitle(file)} title={this.hoverFileTitle(file)}
> >
{collapseIcon} {collapseIcon}
<span <TitleWrapper
className={classNames("is-ellipsis-overflow", classes.title)} className={classNames("is-ellipsis-overflow", "is-size-6")}
> >
{this.renderFileTitle(file)} {this.renderFileTitle(file)}
</span> </TitleWrapper>
{this.renderChangeTag(file)} {this.renderChangeTag(file)}
</div> </FullWidthTitleHeader>
<div className={classNames("level-right", classes.buttonHeader)}> <ButtonWrapper className={classNames("level-right", "is-flex")}>
<ButtonGroup> <ButtonGroup>
<Button <Button
action={this.toggleSideBySide} action={this.toggleSideBySide}
@@ -301,13 +300,13 @@ class DiffFile extends React.Component<Props, State> {
</Button> </Button>
{fileControls} {fileControls}
</ButtonGroup> </ButtonGroup>
</div> </ButtonWrapper>
</div> </FlexWrapLevel>
</div> </div>
{body} {body}
</div> </DiffFilePanel>
); );
} }
} }
export default injectSheets(styles)(translate("repos")(DiffFile)); export default translate("repos")(DiffFile);

View File

@@ -1,60 +1,63 @@
//@flow //@flow
import React from "react"; import React from "react";
import type { Changeset, Repository } from "@scm-manager/ui-types";
import classNames from "classnames";
import { Interpolate, translate } from "react-i18next"; import { Interpolate, translate } from "react-i18next";
import ChangesetId from "./ChangesetId"; import classNames from "classnames";
import injectSheet from "react-jss"; import styled from "styled-components";
import { DateFromNow } from "../..";
import ChangesetAuthor from "./ChangesetAuthor";
import { parseDescription } from "./changesets";
import { AvatarWrapper, AvatarImage } from "../../avatar";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; 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 ChangesetTags from "./ChangesetTags";
import ChangesetButtonGroup from "./ChangesetButtonGroup"; 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 = { type Props = {
repository: Repository, repository: Repository,
changeset: Changeset, 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> { class ChangesetRow extends React.Component<Props> {
createChangesetId = (changeset: Changeset) => { createChangesetId = (changeset: Changeset) => {
const { repository } = this.props; const { repository } = this.props;
@@ -62,28 +65,26 @@ class ChangesetRow extends React.Component<Props> {
}; };
render() { render() {
const { repository, changeset, classes } = this.props; const { repository, changeset } = this.props;
const description = parseDescription(changeset.description); const description = parseDescription(changeset.description);
const changesetId = this.createChangesetId(changeset); const changesetId = this.createChangesetId(changeset);
const dateFromNow = <DateFromNow date={changeset.date} />; const dateFromNow = <DateFromNow date={changeset.date} />;
return ( return (
<div className={classes.changeset}> <Wrapper>
<div className="columns is-gapless is-mobile"> <div className="columns is-gapless is-mobile">
<div className="column is-three-fifths"> <div className="column is-three-fifths">
<div className="columns is-gapless"> <div className="columns is-gapless">
<div className="column is-four-fifths"> <div className="column is-four-fifths">
<div className="media"> <div className="media">
<AvatarWrapper> <AvatarWrapper>
<figure <AvatarFigure className="media-left">
className={classNames(classes.avatarFigure, "media-left")} <FixedSizedAvatar className="image">
>
<div className={classNames("image", classes.avatarImage)}>
<AvatarImage person={changeset.author} /> <AvatarImage person={changeset.author} />
</div> </FixedSizedAvatar>
</figure> </AvatarFigure>
</AvatarWrapper> </AvatarWrapper>
<div className={classNames(classes.metadata, "media-right")}> <Metadata className="media-right">
<h4 className="has-text-weight-bold is-ellipsis-overflow"> <h4 className="has-text-weight-bold is-ellipsis-overflow">
<ExtensionPoint <ExtensionPoint
name="changeset.description" name="changeset.description"
@@ -107,18 +108,18 @@ class ChangesetRow extends React.Component<Props> {
time={dateFromNow} time={dateFromNow}
/> />
</p> </p>
<p className={classNames("is-size-7", classes.authorMargin)}> <AuthorWrapper className="is-size-7">
<ChangesetAuthor changeset={changeset} /> <ChangesetAuthor changeset={changeset} />
</p> </AuthorWrapper>
</div> </Metadata>
</div> </div>
</div> </div>
<div className={classNames("column", classes.isVcentered)}> <VCenteredColumn className="column">
<ChangesetTags changeset={changeset} /> <ChangesetTags changeset={changeset} />
</div> </VCenteredColumn>
</div> </div>
</div> </div>
<div className={classNames("column", classes.flexVcenter)}> <VCenteredChildColumn className={classNames("column", "is-flex")}>
<ChangesetButtonGroup <ChangesetButtonGroup
repository={repository} repository={repository}
changeset={changeset} changeset={changeset}
@@ -128,11 +129,11 @@ class ChangesetRow extends React.Component<Props> {
props={{ repository, changeset }} props={{ repository, changeset }}
renderAll={true} renderAll={true}
/> />
</div> </VCenteredChildColumn>
</div> </div>
</div> </Wrapper>
); );
} }
} }
export default injectSheet(styles)(translate("repos")(ChangesetRow)); export default translate("repos")(ChangesetRow);

View File

@@ -5,6 +5,7 @@ $turquoise: #00d1df;
$blue: #33b2e8; $blue: #33b2e8;
$cyan: $blue; $cyan: $blue;
$green: #00c79b; $green: #00c79b;
$blue-light: #98d8f3;
.is-ellipsis-overflow { .is-ellipsis-overflow {
overflow: hidden; overflow: hidden;
@@ -145,6 +146,10 @@ $danger-25: scale-color($danger, $lightness: 75%);
color: #ffb600 !important; color: #ffb600 !important;
} }
.has-text-blue-light {
color: $blue-light !important;
}
// border and background colors // border and background colors
.has-background-dark-75 { .has-background-dark-75 {
background-color: $dark-75; background-color: $dark-75;
@@ -213,6 +218,9 @@ $danger-25: scale-color($danger, $lightness: 75%);
.has-background-warning-invert { .has-background-warning-invert {
background-color: $warning-invert; background-color: $warning-invert;
} }
.has-background-blue-light {
background-color: $blue-light;
}
// tags // tags
.tag:not(body) { .tag:not(body) {
@@ -589,7 +597,7 @@ form .field:not(.is-grouped) {
.input, .input,
.textarea { .textarea {
/*background-color: whitesmoke;*/ /*background-color: whitesmoke;*/
border-color: #98d8f3; border-color: $blue-light;
box-shadow: none; box-shadow: none;
} }