Replace styled-components with bulma helpers (#1783)

Use Bulma helpers whenever possible instead of custom styled components.
This pull request replaces primarily color definitions, spacing and flex instructions.
This commit is contained in:
Florian Scholdei
2021-09-15 17:40:08 +02:00
committed by GitHub
parent 8a65660278
commit 2cb006d040
97 changed files with 1931 additions and 2244 deletions

View File

@@ -0,0 +1,2 @@
- type: Changed
description: Replace styled-components with bulma helpers ([#1783](https://github.com/scm-manager/scm-manager/pull/1783))

View File

@@ -27,18 +27,14 @@ import { Repository, Link } from "@scm-manager/ui-types";
import { ButtonAddons, Button } from "@scm-manager/ui-components"; import { ButtonAddons, Button } from "@scm-manager/ui-components";
import CloneInformation from "./CloneInformation"; import CloneInformation from "./CloneInformation";
const Wrapper = styled.div`
position: relative;
`;
const Switcher = styled(ButtonAddons)` const Switcher = styled(ButtonAddons)`
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
`; `;
const SmallButton = styled(Button).attrs(props => ({ const SmallButton = styled(Button).attrs((props) => ({
className: "is-small" className: "is-small",
}))` }))`
height: inherit; height: inherit;
`; `;
@@ -70,13 +66,13 @@ export default class ProtocolInformation extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
selected: selectHttpOrFirst(props.repository) selected: selectHttpOrFirst(props.repository),
}; };
} }
selectProtocol = (protocol: Link) => { selectProtocol = (protocol: Link) => {
this.setState({ this.setState({
selected: protocol selected: protocol,
}); });
}; };
@@ -116,10 +112,10 @@ export default class ProtocolInformation extends React.Component<Props, State> {
} }
return ( return (
<Wrapper> <div className="is-relative">
<Switcher>{protocols.map(this.renderProtocolButton)}</Switcher> <Switcher>{protocols.map(this.renderProtocolButton)}</Switcher>
{cloneInformation} {cloneInformation}
</Wrapper> </div>
); );
} }
} }

View File

@@ -44,10 +44,6 @@ const MinWidthControl = styled.div`
min-width: 10rem; min-width: 10rem;
`; `;
const NoBottomMarginField = styled.div`
margin-bottom: 0 !important;
`;
const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, label, disabled }) => { const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, label, disabled }) => {
if (branches) { if (branches) {
return ( return (
@@ -56,7 +52,7 @@ const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, l
<label className={classNames("label", "is-size-6")}>{label}</label> <label className={classNames("label", "is-size-6")}>{label}</label>
</ZeroflexFieldLabel> </ZeroflexFieldLabel>
<div className="field-body"> <div className="field-body">
<NoBottomMarginField className={classNames("field", "is-narrow")}> <div className={classNames("field", "is-narrow", "mb-0")}>
<MinWidthControl className="control"> <MinWidthControl className="control">
<Select <Select
className="is-fullwidth" className="is-fullwidth"
@@ -67,7 +63,7 @@ const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, l
addValueToOptions={true} addValueToOptions={true}
/> />
</MinWidthControl> </MinWidthControl>
</NoBottomMarginField> </div>
</div> </div>
</div> </div>
); );

View File

@@ -21,7 +21,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
@@ -30,6 +29,7 @@ import repository from "./__resources__/repository";
// @ts-ignore ignore unknown png // @ts-ignore ignore unknown png
import Git from "./__resources__/git-logo.png"; import Git from "./__resources__/git-logo.png";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import Icon from "./Icon";
const Wrapper = styled.div` const Wrapper = styled.div`
margin: 2rem; margin: 2rem;
@@ -42,10 +42,15 @@ const longPath =
"dream-path/src/main/scm-plugins/javaUtilityHomeHousingLinkReferrer/sonia/scm/repositoryUndergroundSupportManager/spi/SvnRepositoryServiceResolver.java"; "dream-path/src/main/scm-plugins/javaUtilityHomeHousingLinkReferrer/sonia/scm/repositoryUndergroundSupportManager/spi/SvnRepositoryServiceResolver.java";
const baseUrl = "scm-manager.org/scm/repo/hitchhiker/heartOfGold/sources"; const baseUrl = "scm-manager.org/scm/repo/hitchhiker/heartOfGold/sources";
const sources = Git; const sources = Git;
const prefix = (
<a href="#link">
<Icon name="heart" color="danger" />
</a>
);
storiesOf("BreadCrumb", module) storiesOf("BreadCrumb", module)
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) .addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>) .addDecorator((storyFn) => <Wrapper>{storyFn()}</Wrapper>)
.add("Default", () => ( .add("Default", () => (
<Breadcrumb <Breadcrumb
repository={repository} repository={repository}
@@ -69,4 +74,17 @@ storiesOf("BreadCrumb", module)
revision={"1"} revision={"1"}
permalink={"/" + longPath} permalink={"/" + longPath}
/> />
))
.add("With prefix button", () => (
<Breadcrumb
repository={repository}
defaultBranch={master}
branch={master}
path={path}
baseUrl={baseUrl}
sources={sources}
revision={"1"}
permalink={"/" + path}
preButtons={prefix}
/>
)); ));

View File

@@ -93,14 +93,7 @@ const HomeIcon = styled(Icon)`
`; `;
const ActionBar = styled.div` const ActionBar = styled.div`
align-self: center;
/* order actionbar items horizontal */
display: flex;
justify-content: flex-start;
/* ensure space between action bar items */ /* ensure space between action bar items */
& > * { & > * {
/* /*
* We have to use important, because plugins could use field or control classes like the editor-plugin does. * We have to use important, because plugins could use field or control classes like the editor-plugin does.
@@ -113,7 +106,6 @@ const ActionBar = styled.div`
const PrefixButton = styled.div` const PrefixButton = styled.div`
border-right: 1px solid lightgray; border-right: 1px solid lightgray;
margin-right: 0.5rem;
`; `;
const Breadcrumb: FC<Props> = ({ const Breadcrumb: FC<Props> = ({
@@ -176,7 +168,7 @@ const Breadcrumb: FC<Props> = ({
const renderBreadcrumbNav = () => { const renderBreadcrumbNav = () => {
let prefixButtons = null; let prefixButtons = null;
if (preButtons) { if (preButtons) {
prefixButtons = <PrefixButton>{preButtons}</PrefixButton>; prefixButtons = <PrefixButton className="mr-2">{preButtons}</PrefixButton>;
} }
let homeUrl = baseUrl + "/"; let homeUrl = baseUrl + "/";
@@ -227,13 +219,7 @@ const Breadcrumb: FC<Props> = ({
binder.hasExtension<extensionPoints.ReposSourcesEmptyActionbar>("repos.sources.empty.actionbar") && binder.hasExtension<extensionPoints.ReposSourcesEmptyActionbar>("repos.sources.empty.actionbar") &&
sources?._embedded?.children?.length === 0 sources?._embedded?.children?.length === 0
) { ) {
return ( return <ExtensionPoint name="repos.sources.empty.actionbar" props={{ repository, sources }} renderAll={true} />;
<ExtensionPoint
name="repos.sources.empty.actionbar"
props={{ repository, sources }}
renderAll={true}
/>
);
} }
if (binder.hasExtension<extensionPoints.ReposSourcesActionbar>("repos.sources.actionbar")) { if (binder.hasExtension<extensionPoints.ReposSourcesActionbar>("repos.sources.actionbar")) {
return <ExtensionPoint name="repos.sources.actionbar" props={extProps} renderAll={true} />; return <ExtensionPoint name="repos.sources.actionbar" props={extProps} renderAll={true} />;
@@ -243,11 +229,15 @@ const Breadcrumb: FC<Props> = ({
return ( return (
<> <>
<div className="is-flex is-align-items-center is-justify-content-flex-end"> <div className={classNames("is-flex", "is-justify-content-flex-end", "is-align-items-center")}>
{renderBreadcrumbNav()} {renderBreadcrumbNav()}
{<ActionBar className="my-2">{renderExtensionPoints()}</ActionBar>} {
<ActionBar className={classNames("is-flex", "is-justify-content-flex-start", "is-align-self-center", "my-2")}>
{renderExtensionPoints()}
</ActionBar>
}
</div> </div>
<hr className="is-marginless" /> <hr className="m-0" />
</> </>
); );
}; };

View File

@@ -44,29 +44,6 @@ const NoEventWrapper = styled.article`
z-index: 1; 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 ContentRight = styled.div`
margin-left: auto;
`;
const RightMarginDiv = styled.div`
margin-right: 0.5rem;
`;
const InheritFlexShrinkDiv = styled.div` const InheritFlexShrinkDiv = styled.div`
flex-shrink: inherit; flex-shrink: inherit;
pointer-events: all; pointer-events: all;
@@ -81,11 +58,11 @@ const CardColumn: FC<Props> = ({
footerLeft, footerLeft,
footerRight, footerRight,
action, action,
className className,
}) => { }) => {
const renderAvatar = avatar ? <AvatarWrapper className="media-left">{avatar}</AvatarWrapper> : null; const renderAvatar = avatar ? <figure className="media-left mt-3 ml-4">{avatar}</figure> : null;
const renderDescription = description ? <p className="shorten-text">{description}</p> : null; const renderDescription = description ? <p className="shorten-text">{description}</p> : null;
const renderContentRight = contentRight ? <ContentRight>{contentRight}</ContentRight> : null; const renderContentRight = contentRight ? <div className="ml-auto">{contentRight}</div> : null;
let createLink = null; let createLink = null;
if (link) { if (link) {
@@ -94,7 +71,7 @@ const CardColumn: FC<Props> = ({
createLink = ( createLink = (
<a <a
className="overlay-column" className="overlay-column"
onClick={e => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
action(); action();
}} }}
@@ -108,7 +85,16 @@ const CardColumn: FC<Props> = ({
{createLink} {createLink}
<NoEventWrapper className={classNames("media", className)}> <NoEventWrapper className={classNames("media", className)}>
{renderAvatar} {renderAvatar}
<FlexFullHeight className={classNames("media-content", "text-box", "is-flex")}> <div
className={classNames(
"media-content",
"text-box",
"is-flex",
"is-flex-direction-column",
"is-justify-content-space-around",
"is-align-self-stretch"
)}
>
<div className="is-flex"> <div className="is-flex">
<div className="is-clipped mb-0"> <div className="is-clipped mb-0">
<p className="shorten-text m-0">{title}</p> <p className="shorten-text m-0">{title}</p>
@@ -116,13 +102,13 @@ const CardColumn: FC<Props> = ({
</div> </div>
{renderContentRight} {renderContentRight}
</div> </div>
<FooterWrapper className={classNames("level", "is-flex")}> <div className={classNames("level", "is-flex", "pb-4")}>
<RightMarginDiv className="level-left is-hidden-mobile">{footerLeft}</RightMarginDiv> <div className={classNames("level-left", "is-hidden-mobile", "mr-2")}>{footerLeft}</div>
<InheritFlexShrinkDiv className="level-right is-block is-mobile m-0 shorten-text"> <InheritFlexShrinkDiv className="level-right is-block is-mobile m-0 shorten-text">
{footerRight} {footerRight}
</InheritFlexShrinkDiv> </InheritFlexShrinkDiv>
</FooterWrapper> </div>
</FlexFullHeight> </div>
</NoEventWrapper> </NoEventWrapper>
</> </>
); );

View File

@@ -24,7 +24,6 @@
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components";
type Props = { type Props = {
name: ReactNode; name: ReactNode;
@@ -36,25 +35,17 @@ type State = {
collapsed: boolean; collapsed: boolean;
}; };
const Container = styled.div`
margin-bottom: 1em;
`;
const Wrapper = styled.div`
padding: 0 0.75rem;
`;
export default class CardColumnGroup extends React.Component<Props, State> { export default class CardColumnGroup extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
collapsed: false collapsed: false,
}; };
} }
toggleCollapse = () => { toggleCollapse = () => {
this.setState(prevState => ({ this.setState((prevState) => ({
collapsed: !prevState.collapsed collapsed: !prevState.collapsed,
})); }));
}; };
@@ -89,13 +80,13 @@ export default class CardColumnGroup extends React.Component<Props, State> {
} }
return ( return (
<Container> <div className="mb-4">
<h2> <h2>
<span className={classNames("is-size-4", "has-cursor-pointer")} onClick={this.toggleCollapse}> <span className={classNames("is-size-4", "is-clickable")} onClick={this.toggleCollapse}>
<i className={classNames("fa", icon)} /> <i className={classNames("fa", icon)} />
</span>{" "} </span>{" "}
{url ? ( {url ? (
<Link to={url} className={"has-text-dark"}> <Link to={url} className="has-text-dark">
{name} {name}
</Link> </Link>
) : ( ) : (
@@ -103,9 +94,9 @@ export default class CardColumnGroup extends React.Component<Props, State> {
)} )}
</h2> </h2>
<hr /> <hr />
<Wrapper className={classNames("columns", "card-columns", "is-multiline")}>{content}</Wrapper> <div className={classNames("columns", "card-columns", "is-multiline", "mx-3", "my-0")}>{content}</div>
<div className="is-clearfix" /> <div className="is-clearfix" />
</Container> </div>
); );
} }
} }

View File

@@ -35,7 +35,7 @@ const Wrapper = styled.div`
const link = "/foo/bar"; const link = "/foo/bar";
const icon = <Icon name="icons fa-2x fa-fw" />; const icon = <Icon name="icons fa-2x fa-fw" />;
const contentLeft = <strong className="is-marginless">main content</strong>; const contentLeft = <strong className="m-0">main content</strong>;
const contentRight = <small>more text</small>; const contentRight = <small>more text</small>;
storiesOf("CardColumnSmall", module) storiesOf("CardColumnSmall", module)

View File

@@ -35,22 +35,6 @@ type Props = {
footer?: ReactNode; footer?: ReactNode;
}; };
const FlexFullHeight = styled.div`
flex-direction: column;
justify-content: space-around;
align-self: stretch;
`;
const ContentLeft = styled.div`
margin-bottom: 0 !important;
overflow: hidden;
`;
const ContentRight = styled.div`
margin-left: auto;
align-items: start;
`;
const StyledLink = styled(Link)` const StyledLink = styled(Link)`
color: inherit; color: inherit;
:hover { :hover {
@@ -58,25 +42,30 @@ const StyledLink = styled(Link)`
} }
`; `;
const AvatarWrapper = styled.figure`
margin-right: 0.5rem;
`;
const CardColumnSmall: FC<Props> = ({ link, avatar, contentLeft, contentRight, footer }) => { const CardColumnSmall: FC<Props> = ({ link, avatar, contentLeft, contentRight, footer }) => {
const renderAvatar = avatar ? <AvatarWrapper className="media-left">{avatar}</AvatarWrapper> : null; const renderAvatar = avatar ? <figure className={classNames("media-left", "mr-2")}>{avatar}</figure> : null;
const renderFooter = footer ? <small>{footer}</small> : null; const renderFooter = footer ? <small>{footer}</small> : null;
return ( return (
<StyledLink to={link}> <StyledLink to={link}>
<div className="media"> <div className="media">
{renderAvatar} {renderAvatar}
<FlexFullHeight className={classNames("media-content", "text-box", "is-flex")}> <div
<div className="is-flex is-align-items-center"> className={classNames(
<ContentLeft>{contentLeft}</ContentLeft> "media-content",
<ContentRight>{contentRight}</ContentRight> "text-box",
"is-flex",
"is-flex-direction-column",
"is-justify-content-space-around",
"is-align-self-stretch"
)}
>
<div className={classNames("is-flex", "is-align-items-center")}>
<div className={classNames("is-clipped", "mb-0")}>{contentLeft}</div>
<div className={classNames("is-align-items-start", "ml-auto")}>{contentRight}</div>
</div> </div>
{renderFooter} {renderFooter}
</FlexFullHeight> </div>
</div> </div>
</StyledLink> </StyledLink>
); );

View File

@@ -22,43 +22,38 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React from "react"; import React from "react";
import DateFromNow from "./DateFromNow";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import DateFromNow from "./DateFromNow";
import DateShort from "./DateShort"; import DateShort from "./DateShort";
import styled from "styled-components";
const baseProps = { const baseProps = {
timeZone: "Europe/Berlin", timeZone: "Europe/Berlin",
baseDate: "2019-10-12T13:56:42+02:00" baseDate: "2019-10-12T13:56:42+02:00",
}; };
const dates = [ const dates = [
"2009-06-30T18:30:00+02:00", "2009-06-30T18:30:00+02:00",
"2019-06-30T18:30:00+02:00", "2019-06-30T18:30:00+02:00",
"2019-10-12T13:56:40+02:00", "2019-10-12T13:56:40+02:00",
"2019-10-11T13:56:40+02:00" "2019-10-11T13:56:40+02:00",
]; ];
const Wrapper = styled.div`
padding: 2rem;
`;
storiesOf("Date", module) storiesOf("Date", module)
.add("Date from now", () => ( .add("Date from now", () => (
<Wrapper> <div className="p-5">
{dates.map(d => ( {dates.map((d) => (
<p> <p>
<DateFromNow date={d} {...baseProps} /> <DateFromNow date={d} {...baseProps} />
</p> </p>
))} ))}
</Wrapper> </div>
)) ))
.add("Short", () => ( .add("Short", () => (
<Wrapper> <div className="p-5">
{dates.map(d => ( {dates.map((d) => (
<p> <p>
<DateShort date={d} {...baseProps} /> <DateShort date={d} {...baseProps} />
</p> </p>
))} ))}
</Wrapper> </div>
)); ));

View File

@@ -22,14 +22,15 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, ReactNode, useEffect } from "react"; import React, { FC, ReactNode, useEffect } from "react";
import ErrorNotification from "./ErrorNotification";
import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api";
import { RouteComponentProps, useLocation, withRouter } from "react-router-dom"; import { RouteComponentProps, useLocation, withRouter } from "react-router-dom";
import ErrorPage from "./ErrorPage";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api";
import ErrorNotification from "./ErrorNotification";
import ErrorPage from "./ErrorPage";
import { Subtitle, Title } from "./layout"; import { Subtitle, Title } from "./layout";
import Icon from "./Icon"; import Icon from "./Icon";
import styled from "styled-components";
type State = { type State = {
error?: Error; error?: Error;
@@ -54,9 +55,6 @@ type ErrorDisplayProps = {
}; };
const RedirectIconContainer = styled.div` const RedirectIconContainer = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 256px; min-height: 256px;
`; `;
@@ -69,7 +67,14 @@ const RedirectPage = () => {
<div className="container"> <div className="container">
<Title>{t("errorBoundary.redirect.title")}</Title> <Title>{t("errorBoundary.redirect.title")}</Title>
<Subtitle>{t("errorBoundary.redirect.subtitle")}</Subtitle> <Subtitle>{t("errorBoundary.redirect.subtitle")}</Subtitle>
<RedirectIconContainer className="is-flex"> <RedirectIconContainer
className={classNames(
"is-flex",
"is-flex-direction-column",
"is-justify-content-center",
"is-align-items-center"
)}
>
<Icon name="directions" className="fa-7x" /> <Icon name="directions" className="fa-7x" />
</RedirectIconContainer> </RedirectIconContainer>
</div> </div>
@@ -108,7 +113,7 @@ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: Fallb
const fallbackProps = { const fallbackProps = {
error, error,
errorInfo errorInfo,
}; };
return <FallbackComponent {...fallbackProps} />; return <FallbackComponent {...fallbackProps} />;
@@ -130,7 +135,7 @@ class ErrorBoundary extends React.Component<Props, State> {
componentDidCatch(error: Error, errorInfo: ErrorInfo) { componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({ this.setState({
error, error,
errorInfo errorInfo,
}); });
} }

View File

@@ -21,20 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import styled from "styled-components";
import * as React from "react"; import * as React from "react";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import Help from "./Help"; import Help from "./Help";
const Wrapper = styled.div`
margin: 5rem;
`;
const Spacing = styled.div`
margin-top: 1rem;
`;
const longContent = const longContent =
"Cleverness nuclear genuine static irresponsibility invited President Zaphod\n" + "Cleverness nuclear genuine static irresponsibility invited President Zaphod\n" +
"Beeblebrox hyperspace ship. Another custard through computer-generated universe\n" + "Beeblebrox hyperspace ship. Another custard through computer-generated universe\n" +
@@ -42,17 +32,17 @@ const longContent =
"imaginative generator sweep."; "imaginative generator sweep.";
storiesOf("Help", module) storiesOf("Help", module)
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>) .addDecorator((storyFn) => <div className="m-6">{storyFn()}</div>)
.add("Default", () => <Help message="This is a help message" />) .add("Default", () => <Help message="This is a help message" />)
.add("Multiline", () => ( .add("Multiline", () => (
<> <>
<Spacing> <div className="mt-4">
<label>With multiline (default):</label> <label>With multiline (default):</label>
<Help message={longContent} /> <Help message={longContent} />
</Spacing> </div>
<Spacing> <div className="mt-4">
<label>Without multiline:</label> <label>Without multiline:</label>
<Help message={longContent} multiline={false} /> <Help message={longContent} multiline={false} />
</Spacing> </div>
</> </>
)); ));

View File

@@ -33,22 +33,21 @@ type Props = {
className?: string; className?: string;
}; };
const HelpTooltip = styled(Tooltip)` const AbsolutePositionTooltip = styled(Tooltip)`
position: absolute; position: absolute;
padding-left: 3px;
`; `;
const Help: FC<Props> = ({ message, multiline, className }) => ( const Help: FC<Props> = ({ message, multiline, className }) => (
<HelpTooltip <AbsolutePositionTooltip
className={classNames("is-inline-block", multiline ? "has-tooltip-multiline" : undefined, className)} className={classNames("is-inline-block", "pl-1", multiline ? "has-tooltip-multiline" : undefined, className)}
message={message} message={message}
> >
<HelpIcon /> <HelpIcon />
</HelpTooltip> </AbsolutePositionTooltip>
); );
Help.defaultProps = { Help.defaultProps = {
multiline: true multiline: true,
}; };
export default Help; export default Help;

View File

@@ -23,6 +23,7 @@
*/ */
import React from "react"; import React from "react";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
import Image from "./Image"; import Image from "./Image";
@@ -31,14 +32,10 @@ type Props = WithTranslation & {
}; };
const Wrapper = styled.div` const Wrapper = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 256px; min-height: 256px;
`; `;
const FixedSizedImage = styled(Image)` const FixedSizedImage = styled(Image)`
margin-bottom: 0.75rem;
width: 128px; width: 128px;
height: 128px; height: 128px;
`; `;
@@ -47,8 +44,15 @@ class Loading extends React.Component<Props> {
render() { render() {
const { message, t } = this.props; const { message, t } = this.props;
return ( return (
<Wrapper className="is-flex"> <Wrapper
<FixedSizedImage src="/images/loading.svg" alt={t("loading.alt")} /> className={classNames(
"is-flex",
"is-flex-direction-column",
"is-justify-content-center",
"is-align-items-center"
)}
>
<FixedSizedImage className="mb-3" src="/images/loading.svg" alt={t("loading.alt")} />
<p className="has-text-centered">{message}</p> <p className="has-text-centered">{message}</p>
</Wrapper> </Wrapper>
); );

View File

@@ -60,9 +60,9 @@ const OverviewPageActions: FC<Props> = ({
const link = createAbsoluteLink(inputLink); const link = createAbsoluteLink(inputLink);
const groupSelector = groups && ( const groupSelector = groups && (
<div className={"column is-flex"}> <div className="column is-flex">
<Select <Select
className={"is-fullwidth"} className="is-fullwidth"
options={groups.map((g) => ({ value: g, label: g }))} options={groups.map((g) => ({ value: g, label: g }))}
value={currentGroup} value={currentGroup}
onChange={groupSelected} onChange={groupSelected}
@@ -89,9 +89,9 @@ const OverviewPageActions: FC<Props> = ({
}; };
return ( return (
<div className={"columns is-tablet"}> <div className="columns is-tablet">
{groupSelector} {groupSelector}
<div className={"column"}> <div className="column">
<FilterInput <FilterInput
placeholder={searchPlaceholder} placeholder={searchPlaceholder}
value={urls.getQueryStringFromLocation(location)} value={urls.getQueryStringFromLocation(location)}

View File

@@ -21,37 +21,33 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React from "react"; import React from "react";
import { Icon } from "@scm-manager/ui-components";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import SplitAndReplace from "./SplitAndReplace"; import SplitAndReplace from "./SplitAndReplace";
import { Icon } from "@scm-manager/ui-components";
import styled from "styled-components";
const Wrapper = styled.div`
margin: 2rem;
`;
storiesOf("SplitAndReplace", module).add("Simple replacement", () => { storiesOf("SplitAndReplace", module).add("Simple replacement", () => {
const replacements = [ const replacements = [
{ {
textToReplace: "'", textToReplace: "'",
replacement: <Icon name={"quote-left"} />, replacement: <Icon name={"quote-left"} />,
replaceAll: true replaceAll: true,
}, },
{ {
textToReplace: "`", textToReplace: "`",
replacement: <Icon name={"quote-right"} />, replacement: <Icon name={"quote-right"} />,
replaceAll: true replaceAll: true,
} },
]; ];
return ( return (
<> <>
<Wrapper> <div className="m-6">
<SplitAndReplace text={"'So this is it,` said Arthur, 'We are going to die.`"} replacements={replacements} /> <SplitAndReplace text={"'So this is it,` said Arthur, 'We are going to die.`"} replacements={replacements} />
</Wrapper> </div>
<Wrapper> <div className="m-6">
<SplitAndReplace text={"'Yes,` said Ford, 'except... no! Wait a minute!`"} replacements={replacements} /> <SplitAndReplace text={"'Yes,` said Ford, 'except... no! Wait a minute!`"} replacements={replacements} />
</Wrapper> </div>
</> </>
); );
}); });

View File

@@ -21,10 +21,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, HTMLAttributes } from "react"; import React, { FC } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Color, Size } from "./styleConstants"; import { Color, Size } from "./styleConstants";
import styled, { css } from "styled-components";
type Props = { type Props = {
className?: string; className?: string;
@@ -39,19 +38,7 @@ type Props = {
onRemove?: () => void; onRemove?: () => void;
}; };
type InnerTagProps = HTMLAttributes<HTMLSpanElement> & { const smallClassNames = classNames("p-1", "is-size-7", "has-text-weight-bold");
small: boolean;
};
const smallMixin = css`
font-size: 0.7rem !important;
padding: 0.25rem !important;
font-weight: bold;
`;
const InnerTag = styled.span<InnerTagProps>`
${(props) => props.small && smallMixin};
`;
const Tag: FC<Props> = ({ const Tag: FC<Props> = ({
className, className,
@@ -82,20 +69,26 @@ const Tag: FC<Props> = ({
return ( return (
<> <>
<InnerTag <span
className={classNames("tag", `is-${color}`, `is-${size}`, className, { className={classNames(
"tag",
`is-${color}`,
`is-${size}`,
className,
{
"is-outlined": outlined, "is-outlined": outlined,
"is-rounded": rounded, "is-rounded": rounded,
"has-cursor-pointer": onClick, "is-clickable": onClick,
})} },
size === "small" && smallClassNames
)}
title={title} title={title}
onClick={onClick} onClick={onClick}
small={size === "small"}
> >
{showIcon} {showIcon}
{label} {label}
{children} {children}
</InnerTag> </span>
{showDelete} {showDelete}
</> </>
); );

File diff suppressed because it is too large Load Diff

View File

@@ -24,17 +24,16 @@
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import Button, { ButtonProps } from "./Button"; import Button, { ButtonProps } from "./Button";
import classNames from "classnames";
const Wrapper = styled.div` const Wrapper = styled.div`
margin-top: 2em;
padding: 1em 1em;
border: 2px solid #e9f7fd; border: 2px solid #e9f7fd;
`; `;
export default class CreateButton extends React.Component<ButtonProps> { export default class CreateButton extends React.Component<ButtonProps> {
render() { render() {
return ( return (
<Wrapper className="has-text-centered"> <Wrapper className={classNames("has-text-centered", "mt-5", "p-4")}>
<Button color="primary" {...this.props} /> <Button color="primary" {...this.props} />
</Wrapper> </Wrapper>
); );

View File

@@ -37,20 +37,8 @@ type Props = {
errorMessage: string; errorMessage: string;
}; };
const StyledLevel = styled(Level)`
align-items: stretch;
margin-bottom: 1rem !important; // same margin as field
`;
const FullWidthInputField = styled(InputField)` const FullWidthInputField = styled(InputField)`
width: 100%; width: 100%;
margin-right: 1.5rem;
`;
const StyledField = styled.div.attrs(() => ({
className: "field"
}))`
align-self: flex-end;
`; `;
const AddEntryToTableField: FC<Props> = ({ const AddEntryToTableField: FC<Props> = ({
@@ -60,7 +48,7 @@ const AddEntryToTableField: FC<Props> = ({
fieldLabel, fieldLabel,
helpText, helpText,
validateEntry, validateEntry,
errorMessage errorMessage,
}) => { }) => {
const [entryToAdd, setEntryToAdd] = useState(""); const [entryToAdd, setEntryToAdd] = useState("");
@@ -89,9 +77,11 @@ const AddEntryToTableField: FC<Props> = ({
}; };
return ( return (
<StyledLevel <Level
className="is-align-items-stretch mb-4"
children={ children={
<FullWidthInputField <FullWidthInputField
className="mr-5"
label={fieldLabel} label={fieldLabel}
errorMessage={errorMessage} errorMessage={errorMessage}
onChange={handleAddEntryChange} onChange={handleAddEntryChange}
@@ -103,13 +93,13 @@ const AddEntryToTableField: FC<Props> = ({
/> />
} }
right={ right={
<StyledField> <div className="field is-align-self-flex-end">
<AddButton <AddButton
label={buttonLabel} label={buttonLabel}
action={addButtonClicked} action={addButtonClicked}
disabled={disabled || entryToAdd === "" || !isValid()} disabled={disabled || entryToAdd === "" || !isValid()}
/> />
</StyledField> </div>
} }
/> />
); );

View File

@@ -21,37 +21,31 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import styled from "styled-components";
import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import AddKeyValueEntryToTableField from "./AddKeyValueEntryToTableField";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import { storiesOf } from "@storybook/react";
const Spacing = styled.div` import AddKeyValueEntryToTableField from "./AddKeyValueEntryToTableField";
padding: 2em;
`;
storiesOf("Forms|AddKeyValueEntryToTableField", module) storiesOf("Forms|AddKeyValueEntryToTableField", module)
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) .addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.add("Default", () => ( .add("Default", () => (
<Spacing> <div className="m-6">
<AddKeyValueEntryToTableField <AddKeyValueEntryToTableField
keyFieldLabel="Key" keyFieldLabel="Key"
valueFieldLabel="Value" valueFieldLabel="Value"
buttonLabel="Add to table" buttonLabel="Add to Table"
addEntry={(key, value) => {console.log(key, value)}} addEntry={() => null}
/> />
</Spacing> </div>
)) ))
.add("Disabled", () => ( .add("Disabled", () => (
<Spacing> <div className="m-6">
<AddKeyValueEntryToTableField <AddKeyValueEntryToTableField
keyFieldLabel="Key" keyFieldLabel="Key"
valueFieldLabel="Value" valueFieldLabel="Value"
buttonLabel="Add to table" buttonLabel="Add to Table"
addEntry={() => {}} addEntry={() => null}
disabled={true} disabled={true}
/> />
</Spacing> </div>
)); ));

View File

@@ -21,7 +21,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import InputField from "./InputField"; import InputField from "./InputField";
@@ -39,13 +38,7 @@ type Props = {
validateEntry?: (p: string) => boolean; validateEntry?: (p: string) => boolean;
}; };
const InputLevel = styled.div` const FullWidthInputField = styled(InputField)`
display: flex;
justify-content: space-between;
`;
const StyledInputField = styled(InputField)`
margin-right: 1.5rem;
width: 100%; width: 100%;
`; `;
@@ -62,7 +55,7 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
valueHelpText, valueHelpText,
validateEntry, validateEntry,
errorMessage, errorMessage,
addEntry addEntry,
}) => { }) => {
const [key, setKey] = useState(""); const [key, setKey] = useState("");
const [value, setValue] = useState(""); const [value, setValue] = useState("");
@@ -77,13 +70,14 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
const add = () => { const add = () => {
addEntry(key, value); addEntry(key, value);
setKey("") setKey("");
setValue(""); setValue("");
} };
return ( return (
<InputLevel> <div className="is-flex is-justify-content-space-between">
<StyledInputField <FullWidthInputField
className="mr-5"
label={keyFieldLabel} label={keyFieldLabel}
errorMessage={errorMessage} errorMessage={errorMessage}
onChange={setKey} onChange={setKey}
@@ -93,7 +87,8 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
disabled={disabled} disabled={disabled}
helpText={keyHelpText} helpText={keyHelpText}
/> />
<StyledInputField <FullWidthInputField
className="mr-5"
label={valueFieldLabel} label={valueFieldLabel}
errorMessage={errorMessage} errorMessage={errorMessage}
onChange={setValue} onChange={setValue}
@@ -108,7 +103,7 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
action={add} action={add}
disabled={disabled || !key || !value || !isValid(key) || !isValid(value)} disabled={disabled || !key || !value || !isValid(key) || !isValid(value)}
/> />
</InputLevel> </div>
); );
}; };

View File

@@ -42,7 +42,6 @@ type Props = {
const FullWidthAutocomplete = styled(Autocomplete)` const FullWidthAutocomplete = styled(Autocomplete)`
width: 100%; width: 100%;
margin-right: 1.5rem;
`; `;
const AutocompleteAddEntryToTableField: FC<Props> = ({ const AutocompleteAddEntryToTableField: FC<Props> = ({
@@ -79,6 +78,7 @@ const AutocompleteAddEntryToTableField: FC<Props> = ({
<Level <Level
children={ children={
<FullWidthAutocomplete <FullWidthAutocomplete
className="mr-5"
label={fieldLabel} label={fieldLabel}
loadSuggestions={loadSuggestions} loadSuggestions={loadSuggestions}
valueSelected={handleAddEntryChange} valueSelected={handleAddEntryChange}

View File

@@ -22,18 +22,9 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { ChangeEvent, FC, FocusEvent } from "react"; import React, { ChangeEvent, FC, FocusEvent } from "react";
import { Help } from "../index";
import styled from "styled-components";
import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes";
import classNames from "classnames"; import classNames from "classnames";
import { Help } from "../index";
const StyledRadio = styled.label` import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes";
margin-right: 0.5em;
`;
const InlineFieldset = styled.fieldset`
display: inline-block;
`;
type BaseProps = { type BaseProps = {
label?: string; label?: string;
@@ -47,7 +38,12 @@ type BaseProps = {
readOnly?: boolean; readOnly?: boolean;
}; };
const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ name, defaultChecked, readOnly, ...props }) => { const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({
name,
defaultChecked,
readOnly,
...props
}) => {
const renderHelp = () => { const renderHelp = () => {
const helpText = props.helpText; const helpText = props.helpText;
if (helpText) { if (helpText) {
@@ -76,13 +72,13 @@ const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ name
}; };
return ( return (
<InlineFieldset disabled={readOnly}> <fieldset className="is-inline-block" disabled={readOnly}>
{/* {/*
we have to ignore the next line, we have to ignore the next line,
because jsx label does not the custom disabled attribute because jsx label does not the custom disabled attribute
but bulma does. but bulma does.
// @ts-ignore */} // @ts-ignore */}
<StyledRadio className={classNames("radio", props.className)} disabled={props.disabled}> <label className={classNames("radio", "mr-2", props.className)} disabled={props.disabled}>
<input <input
type="radio" type="radio"
name={name} name={name}
@@ -96,8 +92,8 @@ const InnerRadio: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({ name
/>{" "} />{" "}
{props.label} {props.label}
{renderHelp()} {renderHelp()}
</StyledRadio> </label>
</InlineFieldset> </fieldset>
); );
}; };

View File

@@ -22,25 +22,21 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, ReactNode } from "react"; import React, { FC, ReactNode } from "react";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
type Props = { type Props = {
title: ReactNode; title: ReactNode;
}; };
const Title = styled.div`
font-weight: bold;
margin-bottom: 0.5rem;
`;
const Menu = styled.ul` const Menu = styled.ul`
padding-left: 1.1rem; padding-left: 1.1rem;
`; `;
const FooterSection: FC<Props> = ({ title, children }) => { const FooterSection: FC<Props> = ({ title, children }) => {
return ( return (
<section className="column is-one-third"> <section className={classNames("column", "is-one-third")}>
<Title>{title}</Title> <div className={classNames("has-text-weight-bold", "mb-2")}>{title}</div>
<Menu>{children}</Menu> <Menu>{children}</Menu>
</section> </section>
); );

View File

@@ -21,25 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, ReactNode } from "react"; import React, { FC, ReactNode } from "react";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
const TitleWrapper = styled.div`
display: flex;
align-items: center;
padding: 0.75rem;
font-size: 1rem;
font-weight: bold;
`;
const Separator = styled.div` const Separator = styled.div`
border-bottom: 1px solid rgb(219, 219, 219, 0.5); border-bottom: 1px solid rgb(219, 219, 219, 0.5);
margin: 0 1rem;
`;
const Box = styled.div`
padding: 0.5rem;
`; `;
type Props = { type Props = {
@@ -51,14 +38,16 @@ const GroupEntries: FC<Props> = ({ namespaceHeader, elements }) => {
const content = elements.map((entry, index) => ( const content = elements.map((entry, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<div>{entry}</div> <div>{entry}</div>
{index + 1 !== elements.length ? <Separator /> : null} {index + 1 !== elements.length ? <Separator className="mx-4" /> : null}
</React.Fragment> </React.Fragment>
)); ));
return ( return (
<> <>
<TitleWrapper>{namespaceHeader}</TitleWrapper> <div className={classNames("is-flex", "is-align-items-center", "is-size-6", "has-text-weight-bold", "p-3")}>
<Box className="box">{content}</Box> {namespaceHeader}
</div>
<div className={classNames("box", "p-2")}>{content}</div>
<div className="is-clearfix" /> <div className="is-clearfix" />
</> </>
); );

View File

@@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
import styled from "styled-components";
import Icon from "../Icon"; import Icon from "../Icon";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
@@ -31,16 +30,12 @@ import GroupEntry from "./GroupEntry";
import { Button, ButtonGroup } from "../buttons"; import { Button, ButtonGroup } from "../buttons";
import copyToClipboard from "../CopyToClipboard"; import copyToClipboard from "../CopyToClipboard";
const Wrapper = styled.div`
margin: 2rem;
`;
const link = "/foo/bar"; const link = "/foo/bar";
const icon = <Icon name="icons fa-2x fa-fw" />; const icon = <Icon name="icons fa-2x fa-fw" />;
const name = <strong className="is-marginless">main content</strong>; const name = <strong className="m-0">main content</strong>;
const description = <small>more text</small>; const description = <small>more text</small>;
const longName = ( const longName = (
<strong className="is-marginless"> <strong className="m-0">
Very-important-repository-with-a-particular-long-but-easily-rememberable-name-which-also-is-written-in-kebab-case Very-important-repository-with-a-particular-long-but-easily-rememberable-name-which-also-is-written-in-kebab-case
</strong> </strong>
); );
@@ -56,7 +51,7 @@ const contentRight = (
storiesOf("GroupEntry", module) storiesOf("GroupEntry", module)
.addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) .addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator((storyFn) => <Wrapper>{storyFn()}</Wrapper>) .addDecorator((storyFn) => <div className="m-5">{storyFn()}</div>)
.add("Default", () => ( .add("Default", () => (
<GroupEntry link={link} avatar={icon} name={name} description={description} contentRight={contentRight} /> <GroupEntry link={link} avatar={icon} name={name} description={description} contentRight={contentRight} />
)) ))

View File

@@ -21,18 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, ReactNode } from "react"; import React, { FC, ReactNode } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
const StyledGroupEntry = styled.div` const StyledGroupEntry = styled.div`
max-height: calc(90px - 1.5rem); max-height: calc(90px - 1.5rem);
width: 100%; width: 100%;
display: flex;
justify-content: space-between;
padding: 0.5rem;
align-items: center;
pointer-events: all; pointer-events: all;
`; `;
@@ -49,7 +45,6 @@ const OverlayLink = styled(Link)`
`; `;
const Avatar = styled.div` const Avatar = styled.div`
padding-right: 1rem;
.predefined-avatar { .predefined-avatar {
height: 48px; height: 48px;
width: 48px; width: 48px;
@@ -57,12 +52,7 @@ const Avatar = styled.div`
} }
`; `;
const Name = styled.div`
padding: 0 0.25rem;
`;
const Description = styled.p` const Description = styled.p`
padding: 0 0.25rem;
height: 1.5rem; height: 1.5rem;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow-x: hidden; overflow-x: hidden;
@@ -72,30 +62,14 @@ const Description = styled.p`
`; `;
const ContentLeft = styled.div` const ContentLeft = styled.div`
display: flex;
flex: 1 1 auto;
align-items: center;
min-width: 0; min-width: 0;
`; `;
const ContentRight = styled.div` const ContentRight = styled.div`
display: flex;
flex: 0 0 auto;
justify-content: flex-end;
pointer-events: all; pointer-events: all;
padding-left: 2rem;
margin-bottom: -10px; margin-bottom: -10px;
`; `;
const NameDescriptionWrapper = styled.div`
overflow: hidden;
flex: 1 1 auto;
`;
const Wrapper = styled.div`
position: relative;
`;
type Props = { type Props = {
title?: string; title?: string;
avatar: string | ReactNode; avatar: string | ReactNode;
@@ -107,19 +81,32 @@ type Props = {
const GroupEntry: FC<Props> = ({ link, avatar, title, name, description, contentRight }) => { const GroupEntry: FC<Props> = ({ link, avatar, title, name, description, contentRight }) => {
return ( return (
<Wrapper> <div className="is-relative">
<OverlayLink to={link} /> <OverlayLink to={link} />
<StyledGroupEntry title={title}> <StyledGroupEntry
<ContentLeft> className={classNames("is-flex", "is-justify-content-space-between", "is-align-items-center", "p-2")}
<Avatar>{avatar}</Avatar> title={title}
<NameDescriptionWrapper> >
<Name>{name}</Name> <ContentLeft className={classNames("is-flex", "is-flex-grow-1", "is-align-items-center")}>
<Description>{description}</Description> <Avatar className="mr-4">{avatar}</Avatar>
</NameDescriptionWrapper> <div className={classNames("is-flex-grow-1", "is-clipped")}>
<div className="mx-1">{name}</div>
<Description className="mx-1">{description}</Description>
</div>
</ContentLeft> </ContentLeft>
<ContentRight className="is-hidden-touch">{contentRight}</ContentRight> <ContentRight
className={classNames(
"is-hidden-touch",
"is-flex",
"is-flex-shrink-0",
"is-justify-content-flex-end",
"pl-5"
)}
>
{contentRight}
</ContentRight>
</StyledGroupEntry> </StyledGroupEntry>
</Wrapper> </div>
); );
}; };

View File

@@ -45,22 +45,14 @@ type Props = {
}; };
const PageActionContainer = styled.div` const PageActionContainer = styled.div`
justify-content: flex-end;
align-items: center;
// every child except first // every child except first
> * ~ * { > * ~ * {
margin-left: 1.25rem; margin-left: 1.25rem;
} }
`; `;
const MarginLeft = styled.div` const MaxTitleHeight = styled.div`
margin-left: 0.5rem; // remove blank space in repo create form
`;
const FlexContainer = styled.div`
display: flex;
flex-direction: row;
height: 2.25rem; height: 2.25rem;
`; `;
@@ -98,12 +90,19 @@ export default class Page extends React.Component<Props> {
let pageActions = null; let pageActions = null;
let pageActionsExists = false; let pageActionsExists = false;
React.Children.forEach(children, child => { React.Children.forEach(children, (child) => {
if (child && !error) { if (child && !error) {
if (this.isPageAction(child)) { if (this.isPageAction(child)) {
pageActions = ( pageActions = (
<PageActionContainer <PageActionContainer
className={classNames("column", "is-three-fifths", "is-mobile-action-spacing", "is-flex-tablet")} className={classNames(
"column",
"is-three-fifths",
"is-mobile-action-spacing",
"is-flex-tablet",
"is-justify-content-flex-end",
"is-align-items-center"
)}
> >
{child} {child}
</PageActionContainer> </PageActionContainer>
@@ -119,10 +118,10 @@ export default class Page extends React.Component<Props> {
<> <>
<div className="columns"> <div className="columns">
<div className="column"> <div className="column">
<FlexContainer> <MaxTitleHeight className="is-flex">
<Title title={this.getTextualTitle()}>{this.getTitleComponent()}</Title> <Title title={this.getTextualTitle()}>{this.getTitleComponent()}</Title>
{afterTitle && <MarginLeft>{afterTitle}</MarginLeft>} {afterTitle && <div className="ml-2">{afterTitle}</div>}
</FlexContainer> </MaxTitleHeight>
{subtitle ? <Subtitle>{subtitle}</Subtitle> : null} {subtitle ? <Subtitle>{subtitle}</Subtitle> : null}
</div> </div>
{pageActions} {pageActions}
@@ -145,7 +144,7 @@ export default class Page extends React.Component<Props> {
} }
const content: ReactNode[] = []; const content: ReactNode[] = [];
React.Children.forEach(children, child => { React.Children.forEach(children, (child) => {
if (child) { if (child) {
if (!this.isPageAction(child)) { if (!this.isPageAction(child)) {
content.push(child); content.push(child);

View File

@@ -76,7 +76,7 @@ export const Modal: FC<Props> = ({
<div className="modal-background" onClick={closeFunction} /> <div className="modal-background" onClick={closeFunction} />
<SizedModal className="modal-card" size={size}> <SizedModal className="modal-card" size={size}>
<header className={classNames("modal-card-head", `has-background-${headColor}`)}> <header className={classNames("modal-card-head", `has-background-${headColor}`)}>
<p className={`modal-card-title is-marginless has-text-${headTextColor}`}>{title}</p> <p className={`modal-card-title m-0 has-text-${headTextColor}`}>{title}</p>
<button className="delete" aria-label="close" onClick={closeFunction} /> <button className="delete" aria-label="close" onClick={closeFunction} />
</header> </header>
<section className="modal-card-body">{body}</section> <section className="modal-card-body">{body}</section>

View File

@@ -82,7 +82,7 @@ const SecondaryNavigation: FC<Props> = ({ label, children, collapsible = true })
<SectionContainer className="menu"> <SectionContainer className="menu">
<div> <div>
<MenuLabel <MenuLabel
className={classNames("menu-label", { "has-cursor-pointer": collapsible })} className={classNames("menu-label", { "is-clickable": collapsible })}
collapsed={isCollapsed} collapsed={isCollapsed}
onClick={toggleCollapseState} onClick={toggleCollapseState}
> >

View File

@@ -43,7 +43,7 @@ type ContainerProps = {
const PopoverContainer = styled.div<ContainerProps>` const PopoverContainer = styled.div<ContainerProps>`
position: absolute; position: absolute;
z-index: 100; z-index: 100;
width: ${props => props.width}px; width: ${(props) => props.width}px;
display: block; display: block;
&:before { &:before {
@@ -54,7 +54,7 @@ const PopoverContainer = styled.div<ContainerProps>`
height: 0; height: 0;
width: 0; width: 0;
top: 100%; top: 100%;
left: ${props => props.width / 2}px; left: ${(props) => props.width / 2}px;
border-color: transparent; border-color: transparent;
border-bottom-color: white; border-bottom-color: white;
border-left-color: white; border-left-color: white;
@@ -67,15 +67,11 @@ const PopoverContainer = styled.div<ContainerProps>`
} }
`; `;
const SmallHr = styled.hr`
margin: 0.5em 0;
`;
const PopoverHeading = styled.div` const PopoverHeading = styled.div`
height: 1.5em; height: 1.5em;
`; `;
const Popover: FC<Props> = props => { const Popover: FC<Props> = (props) => {
if (!props.show) { if (!props.show) {
return null; return null;
} }
@@ -93,13 +89,13 @@ const InnerPopover: FC<Props> = ({ title, show, width, offsetTop, offsetLeft, di
const onMouseEnter = () => { const onMouseEnter = () => {
dispatch({ dispatch({
type: "enter-popover" type: "enter-popover",
}); });
}; };
const onMouseLeave = () => { const onMouseLeave = () => {
dispatch({ dispatch({
type: "leave-popover" type: "leave-popover",
}); });
}; };
@@ -109,20 +105,20 @@ const InnerPopover: FC<Props> = ({ title, show, width, offsetTop, offsetLeft, di
<PopoverContainer <PopoverContainer
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
className={"box"} className="box"
style={{ top: `${top}px`, left: `${left}px` }} style={{ top: `${top}px`, left: `${left}px` }}
width={width!} width={width!}
ref={ref} ref={ref}
> >
<PopoverHeading>{title}</PopoverHeading> <PopoverHeading>{title}</PopoverHeading>
<SmallHr /> <hr className="my-2" />
{children} {children}
</PopoverContainer> </PopoverContainer>
); );
}; };
Popover.defaultProps = { Popover.defaultProps = {
width: 120 width: 120,
}; };
export default Popover; export default Popover;

View File

@@ -27,7 +27,6 @@ import Tooltip from "../Tooltip";
const Button = styled.a` const Button = styled.a`
width: 50px; width: 50px;
cursor: pointer;
&:hover { &:hover {
color: #33b2e8; color: #33b2e8;
} }
@@ -47,7 +46,7 @@ const DiffButton: FC<Props> = ({ icon, tooltip, onClick }) => {
return ( return (
<Tooltip message={tooltip} location="top"> <Tooltip message={tooltip} location="top">
<Button aria-label={tooltip} className="button" onClick={handleClick}> <Button aria-label={tooltip} className="button is-clickable" onClick={handleClick}>
<i className={`fas fa-${icon}`} /> <i className={`fas fa-${icon}`} />
</Button> </Button>
</Tooltip> </Tooltip>

View File

@@ -69,22 +69,6 @@ const FullWidthTitleHeader = styled.div`
max-width: 100%; max-width: 100%;
`; `;
const TitleWrapper = styled.span`
margin-left: 0.25rem;
`;
const AlignRight = styled.div`
margin-left: auto;
`;
const HunkDivider = styled.hr`
margin: 0.5rem 0;
`;
const ChangeTypeTag = styled(Tag)`
margin-left: 0.75rem;
`;
const MarginlessModalContent = styled.div` const MarginlessModalContent = styled.div`
margin: -1.25rem; margin: -1.25rem;
@@ -319,7 +303,7 @@ class DiffFile extends React.Component<Props, State> {
} else if (i > 0) { } else if (i > 0) {
items.push( items.push(
<Decoration> <Decoration>
<HunkDivider /> <hr className="my-2" />
</Decoration> </Decoration>
); );
} }
@@ -403,8 +387,8 @@ class DiffFile extends React.Component<Props, State> {
const color = value === "added" ? "success" : value === "deleted" ? "danger" : "info"; const color = value === "added" ? "success" : value === "deleted" ? "danger" : "info";
return ( return (
<ChangeTypeTag <Tag
className={classNames("has-text-weight-normal")} className={classNames("has-text-weight-normal", "ml-3")}
rounded={true} rounded={true}
outlined={true} outlined={true}
color={color} color={color}
@@ -431,7 +415,7 @@ class DiffFile extends React.Component<Props, State> {
const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null; const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null;
const innerContent = ( const innerContent = (
<div className="panel-block is-paddingless"> <div className="panel-block p-0">
{fileAnnotations} {fileAnnotations}
<TokenizedDiffView className={viewType} viewType={viewType} file={file}> <TokenizedDiffView className={viewType} viewType={viewType} file={file}>
{(hunks: HunkType[]) => {(hunks: HunkType[]) =>
@@ -475,13 +459,13 @@ class DiffFile extends React.Component<Props, State> {
</MenuContext.Consumer> </MenuContext.Consumer>
); );
const headerButtons = ( const headerButtons = (
<AlignRight className={classNames("level-right", "is-flex")}> <div className={classNames("level-right", "is-flex", "ml-auto")}>
<ButtonGroup> <ButtonGroup>
{sideBySideToggle} {sideBySideToggle}
{openInFullscreen} {openInFullscreen}
{fileControls} {fileControls}
</ButtonGroup> </ButtonGroup>
</AlignRight> </div>
); );
let errorModal; let errorModal;
@@ -506,14 +490,14 @@ class DiffFile extends React.Component<Props, State> {
<div className="panel-heading"> <div className="panel-heading">
<div className={classNames("level", "is-flex-wrap-wrap")}> <div className={classNames("level", "is-flex-wrap-wrap")}>
<FullWidthTitleHeader <FullWidthTitleHeader
className={classNames("level-left", "is-flex", "has-cursor-pointer")} className={classNames("level-left", "is-flex", "is-clickable")}
onClick={this.toggleCollapse} onClick={this.toggleCollapse}
title={this.hoverFileTitle(file)} title={this.hoverFileTitle(file)}
> >
{collapseIcon} {collapseIcon}
<TitleWrapper className={classNames("is-ellipsis-overflow", "is-size-6")}> <span className={classNames("is-ellipsis-overflow", "is-size-6", "ml-1")}>
{this.renderFileTitle(file)} {this.renderFileTitle(file)}
</TitleWrapper> </span>
{this.renderChangeTag(file)} {this.renderChangeTag(file)}
</FullWidthTitleHeader> </FullWidthTitleHeader>
{headerButtons} {headerButtons}

View File

@@ -43,7 +43,7 @@ const HealthCheckFailureDetail: FC<Props> = ({ active, closeFunction, failures }
return ( return (
<Modal <Modal
body={ body={
<div className={"content"}> <div className="content">
<HealthCheckFailureList failures={failures} /> <HealthCheckFailureList failures={failures} />
</div> </div>
} }

View File

@@ -28,14 +28,12 @@ import styled from "styled-components";
const HunkDivider = styled.div` const HunkDivider = styled.div`
background: #98d8f3; background: #98d8f3;
font-size: 0.7rem;
padding-left: 1.78em;
`; `;
const HunkExpandDivider: FC = ({ children }) => { const HunkExpandDivider: FC = ({ children }) => {
return ( return (
<Decoration> <Decoration>
<HunkDivider>{children}</HunkDivider> <HunkDivider className="is-size-7 pl-5">{children}</HunkDivider>
</Decoration> </Decoration>
); );
}; };

View File

@@ -24,7 +24,6 @@
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components";
type Props = { type Props = {
icon: string; icon: string;
@@ -32,10 +31,6 @@ type Props = {
onClick: () => Promise<any>; onClick: () => Promise<any>;
}; };
const ExpandLink = styled.span`
cursor: pointer;
`;
const HunkExpandLink: FC<Props> = ({ icon, text, onClick }) => { const HunkExpandLink: FC<Props> = ({ icon, text, onClick }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -49,9 +44,9 @@ const HunkExpandLink: FC<Props> = ({ icon, text, onClick }) => {
}; };
return ( return (
<ExpandLink onClick={onClickWithLoadingMarker}> <span className="is-clickable" onClick={onClickWithLoadingMarker}>
<i className={classNames("fa", icon)} /> {loading ? t("diff.expanding") : text} <i className={classNames("fa", icon)} /> {loading ? t("diff.expanding") : text}
</ExpandLink> </span>
); );
}; };

View File

@@ -24,13 +24,11 @@
import React, { FC } from "react"; import React, { FC } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import Tooltip from "../Tooltip"; import Tooltip from "../Tooltip";
import Icon from "../Icon"; import Icon from "../Icon";
const Button = styled(Link)` const Button = styled(Link)`
width: 50px; width: 50px;
cursor: pointer;
&:hover { &:hover {
color: #33b2e8; color: #33b2e8;
} }
@@ -44,7 +42,7 @@ type Props = {
const JumpToFileButton: FC<Props> = ({ link, tooltip }) => { const JumpToFileButton: FC<Props> = ({ link, tooltip }) => {
return ( return (
<Tooltip message={tooltip} location="top"> <Tooltip message={tooltip} location="top">
<Button aria-label={tooltip} className="button" to={link}> <Button aria-label={tooltip} className="button is-clickable" to={link}>
<Icon name="file-code" color="inherit" /> <Icon name="file-code" color="inherit" />
</Button> </Button>
</Tooltip> </Tooltip>

View File

@@ -22,16 +22,14 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { NotFoundError, useDiff } from "@scm-manager/ui-api"; import { NotFoundError, useDiff } from "@scm-manager/ui-api";
import ErrorNotification from "../ErrorNotification"; import ErrorNotification from "../ErrorNotification";
import Notification from "../Notification";
import Loading from "../Loading"; import Loading from "../Loading";
import Notification from "../Notification";
import Button from "../buttons/Button";
import Diff from "./Diff"; import Diff from "./Diff";
import { DiffObjectProps } from "./DiffTypes"; import { DiffObjectProps } from "./DiffTypes";
import { useTranslation } from "react-i18next";
import Button from "../buttons/Button";
import styled from "styled-components";
type Props = DiffObjectProps & { type Props = DiffObjectProps & {
url: string; url: string;
@@ -44,19 +42,15 @@ type NotificationProps = {
isFetchingNextPage: boolean; isFetchingNextPage: boolean;
}; };
const StyledNotification = styled(Notification)`
margin-top: 1.5rem;
`;
const PartialNotification: FC<NotificationProps> = ({ fetchNextPage, isFetchingNextPage }) => { const PartialNotification: FC<NotificationProps> = ({ fetchNextPage, isFetchingNextPage }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
return ( return (
<StyledNotification type="info"> <Notification className="mt-5" type="info">
<div className="columns is-centered"> <div className="columns is-centered">
<div className="column">{t("changesets.moreDiffsAvailable")}</div> <div className="column">{t("changesets.moreDiffsAvailable")}</div>
<Button label={t("changesets.loadMore")} action={fetchNextPage} loading={isFetchingNextPage} /> <Button label={t("changesets.loadMore")} action={fetchNextPage} loading={isFetchingNextPage} />
</div> </div>
</StyledNotification> </Notification>
); );
}; };

View File

@@ -31,6 +31,7 @@ import RepositoryFlags from "./RepositoryFlags";
import styled from "styled-components"; import styled from "styled-components";
import Icon from "../Icon"; import Icon from "../Icon";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames";
type DateProp = Date | string; type DateProp = Date | string;
@@ -43,26 +44,9 @@ type Props = {
const ContentRightContainer = styled.div` const ContentRightContainer = styled.div`
height: calc(80px - 1.5rem); height: calc(80px - 1.5rem);
margin-right: 1rem;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
`;
const DateWrapper = styled.small`
padding-bottom: 0.25rem;
`;
const QuickActionbar = styled.span`
display: flex;
justify-content: flex-end;
align-items: flex-end;
`; `;
const QuickAction = styled(Icon)` const QuickAction = styled(Icon)`
font-size: 1.25rem;
:hover { :hover {
color: #363636 !important; color: #363636 !important;
} }
@@ -80,7 +64,15 @@ const RepositoryEntry: FC<Props> = ({ repository, baseDate }) => {
const [openCloneModal, setOpenCloneModal] = useState(false); const [openCloneModal, setOpenCloneModal] = useState(false);
const createContentRight = () => ( const createContentRight = () => (
<ContentRightContainer> <ContentRightContainer
className={classNames(
"is-flex",
"is-flex-direction-column",
"is-justify-content-space-between",
"is-relative",
"mr-4"
)}
>
{openCloneModal && ( {openCloneModal && (
<Modal <Modal
size="L" size="L"
@@ -98,18 +90,18 @@ const RepositoryEntry: FC<Props> = ({ repository, baseDate }) => {
closeFunction={() => setOpenCloneModal(false)} closeFunction={() => setOpenCloneModal(false)}
/> />
)} )}
<QuickActionbar> <span className={classNames("is-flex", "is-justify-content-flex-end", "is-align-items-flex-end")}>
<QuickAction <QuickAction
className={classNames("is-clickable", "is-size-5")}
name="download" name="download"
color="info" color="info"
className="has-cursor-pointer"
onClick={() => setOpenCloneModal(true)} onClick={() => setOpenCloneModal(true)}
title={t("overview.clone")} title={t("overview.clone")}
/> />
</QuickActionbar> </span>
<DateWrapper> <small className="pb-1">
<DateFromNow baseDate={baseDate} date={repository.lastModified || repository.creationDate} /> <DateFromNow baseDate={baseDate} date={repository.lastModified || repository.creationDate} />
</DateWrapper> </small>
</ContentRightContainer> </ContentRightContainer>
); );

View File

@@ -23,7 +23,6 @@
*/ */
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import styled from "styled-components";
import { Icon } from "@scm-manager/ui-components"; import { Icon } from "@scm-manager/ui-components";
import Tooltip from "../Tooltip"; import Tooltip from "../Tooltip";
@@ -33,10 +32,6 @@ type Props = {
tooltip?: string; tooltip?: string;
}; };
const PointerEventsLink = styled(Link)`
pointer-events: all;
`;
class RepositoryEntryLink extends React.Component<Props> { class RepositoryEntryLink extends React.Component<Props> {
render() { render() {
const { to, icon, tooltip } = this.props; const { to, icon, tooltip } = this.props;
@@ -51,9 +46,9 @@ class RepositoryEntryLink extends React.Component<Props> {
} }
return ( return (
<PointerEventsLink className="level-item" to={to}> <Link className="level-item is-clickable" to={to}>
{content} {content}
</PointerEventsLink> </Link>
); );
} }
} }

View File

@@ -21,15 +21,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import RepositoryFlag from "./RepositoryFlag";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { Repository } from "@scm-manager/ui-types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
import HealthCheckFailureDetail from "./HealthCheckFailureDetail"; import { Repository } from "@scm-manager/ui-types";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { TooltipLocation } from "../Tooltip"; import { TooltipLocation } from "../Tooltip";
import RepositoryFlag from "./RepositoryFlag";
import HealthCheckFailureDetail from "./HealthCheckFailureDetail";
type Props = { type Props = {
repository: Repository; repository: Repository;
@@ -37,11 +37,6 @@ type Props = {
tooltipLocation?: TooltipLocation; tooltipLocation?: TooltipLocation;
}; };
const Wrapper = styled.span`
display: flex;
align-items: center;
`;
const RepositoryFlagContainer = styled.div` const RepositoryFlagContainer = styled.div`
.tag { .tag {
margin-left: 0.25rem; margin-left: 0.25rem;
@@ -91,13 +86,13 @@ const RepositoryFlags: FC<Props> = ({ repository, className, tooltipLocation = "
); );
return ( return (
<Wrapper> <span className={classNames("is-flex", "is-align-items-center", className)}>
{modal} {modal}
<RepositoryFlagContainer> <RepositoryFlagContainer>
{repositoryFlags} {repositoryFlags}
<ExtensionPoint name="repository.flags" props={{ repository, tooltipLocation }} renderAll={true} /> <ExtensionPoint name="repository.flags" props={{ repository, tooltipLocation }} renderAll={true} />
</RepositoryFlagContainer> </RepositoryFlagContainer>
</Wrapper> </span>
); );
}; };

View File

@@ -36,7 +36,6 @@ import Popover from "./Popover";
import AnnotateLine from "./AnnotateLine"; import AnnotateLine from "./AnnotateLine";
import { Action } from "./actions"; import { Action } from "./actions";
import { determineLanguage } from "../../languages"; import { determineLanguage } from "../../languages";
import styled from "styled-components";
type Props = { type Props = {
source: AnnotatedSource; source: AnnotatedSource;
@@ -57,11 +56,6 @@ const initialState = {
onLine: false, onLine: false,
}; };
const NoSpacingReactSyntaxHighlighter = styled(ReactSyntaxHighlighter)`
margin: 0 !important;
padding: 0 !important;
`;
const reducer = (state: State, action: Action): State => { const reducer = (state: State, action: Action): State => {
switch (action.type) { switch (action.type) {
case "enter-line": { case "enter-line": {
@@ -152,14 +146,15 @@ const Annotate: FC<Props> = ({ source, repository, baseDate }) => {
return ( return (
<div className="panel-block"> <div className="panel-block">
{popover} {popover}
<NoSpacingReactSyntaxHighlighter <ReactSyntaxHighlighter
className="m-0 p-0"
showLineNumbers={false} showLineNumbers={false}
language={determineLanguage(source.language)} language={determineLanguage(source.language)}
style={highlightingTheme} style={highlightingTheme}
renderer={defaultRenderer} renderer={defaultRenderer}
> >
{code} {code}
</NoSpacingReactSyntaxHighlighter> </ReactSyntaxHighlighter>
</div> </div>
); );
}; };

View File

@@ -44,8 +44,6 @@ const Author = styled(LineElement)`
`; `;
const When = styled(LineElement)` const When = styled(LineElement)`
display: inline-block;
width: 6.5em; width: 6.5em;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@@ -31,7 +31,7 @@ import { DateInput } from "../../useDateFormatter";
import { Repository, AnnotatedLine } from "@scm-manager/ui-types"; import { Repository, AnnotatedLine } from "@scm-manager/ui-types";
import AuthorImage from "./AuthorImage"; import AuthorImage from "./AuthorImage";
import { Action } from "./actions"; import { Action } from "./actions";
import {useTranslation} from "react-i18next"; import { useTranslation } from "react-i18next";
const PopoverContainer = styled.div` const PopoverContainer = styled.div`
position: absolute; position: absolute;
@@ -62,16 +62,11 @@ const PopoverContainer = styled.div`
} }
`; `;
const SmallHr = styled.hr`
margin: 0.5em 0;
`;
const PopoverHeading = styled.div` const PopoverHeading = styled.div`
height: 1.5em; height: 1.5em;
`; `;
const PopoverDescription = styled.p` const PopoverDescription = styled.p`
margin-top: 0.5em;
overflow-wrap: break-word; overflow-wrap: break-word;
`; `;
@@ -102,13 +97,13 @@ const Popover: FC<PopoverProps> = ({ annotation, offsetTop, repository, baseDate
const onMouseEnter = () => { const onMouseEnter = () => {
dispatch({ dispatch({
type: "enter-popover" type: "enter-popover",
}); });
}; };
const OnMouseLeave = () => { const OnMouseLeave = () => {
dispatch({ dispatch({
type: "leave-popover" type: "leave-popover",
}); });
}; };
@@ -128,14 +123,14 @@ const Popover: FC<PopoverProps> = ({ annotation, offsetTop, repository, baseDate
</span> </span>
<DateFromNow className="is-pulled-right" date={annotation.when} baseDate={baseDate} /> <DateFromNow className="is-pulled-right" date={annotation.when} baseDate={baseDate} />
</PopoverHeading> </PopoverHeading>
<SmallHr /> <hr className="my-2" />
<p> <p>
{t("changeset.label") + " "} {t("changeset.label") + " "}
<Link to={`/repo/${repository.namespace}/${repository.name}/code/changeset/${annotation.revision}`}> <Link to={`/repo/${repository.namespace}/${repository.name}/code/changeset/${annotation.revision}`}>
{shortRevision(annotation.revision)} {shortRevision(annotation.revision)}
</Link> </Link>
</p> </p>
<PopoverDescription className="content">{annotation.description}</PopoverDescription> <PopoverDescription className="content mt-2">{annotation.description}</PopoverDescription>
</PopoverContainer> </PopoverContainer>
); );
}; };

View File

@@ -38,7 +38,7 @@ class ChangesetButtonGroup extends React.Component<Props> {
const changesetLink = createChangesetLink(repository, changeset); const changesetLink = createChangesetLink(repository, changeset);
const sourcesLink = createSourcesLink(repository, changeset); const sourcesLink = createSourcesLink(repository, changeset);
return ( return (
<ButtonAddons className="is-marginless"> <ButtonAddons className="m-0">
<Button link={changesetLink} icon="exchange-alt" label={t("changeset.buttons.details")} reducedMobile={true} /> <Button link={changesetLink} icon="exchange-alt" label={t("changeset.buttons.details")} reducedMobile={true} />
<Button link={sourcesLink} icon="code" label={t("changeset.buttons.sources")} reducedMobile={true} /> <Button link={sourcesLink} icon="code" label={t("changeset.buttons.sources")} reducedMobile={true} />
</ButtonAddons> </ButtonAddons>

View File

@@ -52,39 +52,15 @@ const Wrapper = styled.div`
} }
`; `;
const AvatarFigure = styled.figure`
margin-top: 0.5rem;
margin-right: 0.5rem;
`;
const FixedSizedAvatar = styled.div` const FixedSizedAvatar = styled.div`
width: 35px; width: 35px;
height: 35px; height: 35px;
`; `;
const Metadata = styled.div` const FullWidthDiv = styled.div`
margin-left: 0;
width: 100%; width: 100%;
`; `;
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;
`;
const FlexRow = styled.div`
display: flex;
flex-direction: row;
`;
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;
@@ -99,25 +75,25 @@ class ChangesetRow extends React.Component<Props> {
return ( return (
<Wrapper> <Wrapper>
<div className="columns is-gapless is-mobile"> <div className={classNames("columns", "is-gapless", "is-mobile")}>
<div className="column is-three-fifths"> <div className={classNames("column", "is-three-fifths")}>
<div className="columns is-gapless"> <div className={classNames("columns", "is-gapless")}>
<div className="column is-four-fifths"> <div className={classNames("column", "is-four-fifths")}>
<div className="media"> <div className="media">
<AvatarWrapper> <AvatarWrapper>
<AvatarFigure className="media-left"> <figure className={classNames("media-left", "mt-2", "mr-2")}>
<FixedSizedAvatar className="image"> <FixedSizedAvatar className="image">
<AvatarImage person={changeset.author} /> <AvatarImage person={changeset.author} />
</FixedSizedAvatar> </FixedSizedAvatar>
</AvatarFigure> </figure>
</AvatarWrapper> </AvatarWrapper>
<Metadata className="media-right"> <FullWidthDiv className={classNames("media-right", "ml-0")}>
<h4 className="has-text-weight-bold is-ellipsis-overflow"> <h4 className={classNames("has-text-weight-bold", "is-ellipsis-overflow")}>
<ExtensionPoint <ExtensionPoint
name="changeset.description" name="changeset.description"
props={{ props={{
changeset, changeset,
value: description.title value: description.title,
}} }}
renderAll={false} renderAll={false}
> >
@@ -130,36 +106,33 @@ class ChangesetRow extends React.Component<Props> {
<p className="is-hidden-desktop"> <p className="is-hidden-desktop">
<Trans i18nKey="repos:changeset.shortSummary" components={[changesetId, dateFromNow]} /> <Trans i18nKey="repos:changeset.shortSummary" components={[changesetId, dateFromNow]} />
</p> </p>
<FlexRow> <div className="is-flex">
<AuthorWrapper className="is-size-7 is-ellipsis-overflow"> <p className={classNames("is-size-7", "is-ellipsis-overflow", "mt-2")}>
<ChangesetAuthor changeset={changeset} /> <ChangesetAuthor changeset={changeset} />
</AuthorWrapper> </p>
{changeset?.signatures && changeset.signatures.length > 0 && ( {changeset?.signatures && changeset.signatures.length > 0 && (
<SignatureIcon <SignatureIcon className={classNames("mx-2", "pt-1")} signatures={changeset.signatures} />
className="mx-2 pt-1"
signatures={changeset.signatures}
/>
)} )}
</FlexRow> </div>
</Metadata> </FullWidthDiv>
</div> </div>
</div> </div>
<VCenteredColumn className="column"> <div className={classNames("column", "is-align-self-center")}>
<ChangesetTags changeset={changeset} /> <ChangesetTags changeset={changeset} />
</VCenteredColumn>
</div> </div>
</div> </div>
<VCenteredChildColumn className={classNames("column", "is-flex")}> </div>
<div className={classNames("column", "is-flex", "is-justify-content-flex-end", "is-align-items-center")}>
<ChangesetButtonGroup repository={repository} changeset={changeset} /> <ChangesetButtonGroup repository={repository} changeset={changeset} />
<ExtensionPoint <ExtensionPoint
name="changeset.right" name="changeset.right"
props={{ props={{
repository, repository,
changeset changeset,
}} }}
renderAll={true} renderAll={true}
/> />
</VCenteredChildColumn> </div>
</div> </div>
</Wrapper> </Wrapper>
); );

View File

@@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import styled from "styled-components"; import classNames from "classnames";
import Icon from "../Icon"; import Icon from "../Icon";
type Props = { type Props = {
@@ -30,13 +30,8 @@ type Props = {
isVisible: boolean; isVisible: boolean;
}; };
const IconWithMarginLeft = styled(Icon)`
visibility: ${(props: Props) => (props.isVisible ? "visible" : "hidden")};
margin-left: 0.25em;
`;
const SortIcon: FC<Props> = (props: Props) => { const SortIcon: FC<Props> = (props: Props) => {
return <IconWithMarginLeft name={props.name} isVisible={props.isVisible} />; return <Icon className={classNames("ml-1", { "is-invisible": !props.isVisible })} name={props.name} />;
}; };
export default SortIcon; export default SortIcon;

View File

@@ -109,7 +109,7 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
<tr> <tr>
{React.Children.map(children, (child, index) => ( {React.Children.map(children, (child, index) => (
<th <th
className={isSortable(child) && "has-cursor-pointer"} className={isSortable(child) && "is-clickable"}
onClick={isSortable(child) ? () => tableSort(index) : undefined} onClick={isSortable(child) ? () => tableSort(index) : undefined}
onMouseEnter={() => setHoveredColumnIndex(index)} onMouseEnter={() => setHoveredColumnIndex(index)}
onMouseLeave={() => setHoveredColumnIndex(undefined)} onMouseLeave={() => setHoveredColumnIndex(undefined)}

View File

@@ -59,11 +59,6 @@ const Container = styled.div<Themeable>`
} }
`; `;
const Title = styled.h1<Themeable>`
margin-bottom: 0.25rem;
font-weight: bold;
`;
const Toast: FC<Props> = ({ children, title, type }) => { const Toast: FC<Props> = ({ children, title, type }) => {
const rootElement = usePortalRootElement("toastRoot"); const rootElement = usePortalRootElement("toastRoot");
if (!rootElement) { if (!rootElement) {
@@ -74,7 +69,7 @@ const Toast: FC<Props> = ({ children, title, type }) => {
const theme = getTheme(type); const theme = getTheme(type);
const content = ( const content = (
<Container theme={theme}> <Container theme={theme}>
<Title theme={theme}>{title}</Title> <h1 className="mb-1 has-text-weight-bold">{title}</h1>
<ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider> <ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider>
</Container> </Container>
); );

View File

@@ -23,6 +23,7 @@
*/ */
import React, { FC, useContext, MouseEvent } from "react"; import React, { FC, useContext, MouseEvent } from "react";
import { ToastThemeContext, Themeable } from "./themes"; import { ToastThemeContext, Themeable } from "./themes";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
type Props = { type Props = {
@@ -30,25 +31,21 @@ type Props = {
onClick?: () => void; onClick?: () => void;
}; };
const ThemedButton = styled.button.attrs(props => ({ const ThemedButton = styled.button.attrs((props) => ({
className: "button" className: "button",
}))<Themeable>` }))<Themeable>`
color: ${props => props.theme.primary}; color: ${(props) => props.theme.primary};
border-color: ${props => props.theme.primary}; border-color: ${(props) => props.theme.primary};
background-color: ${props => props.theme.secondary}; background-color: ${(props) => props.theme.secondary};
font-size: 0.75rem; font-size: 0.75rem;
&:hover { &:hover {
color: ${props => props.theme.primary}; color: ${(props) => props.theme.primary};
border-color: ${props => props.theme.tertiary}; border-color: ${(props) => props.theme.tertiary};
background-color: ${props => props.theme.tertiary}; background-color: ${(props) => props.theme.tertiary};
} }
`; `;
const ToastButtonIcon = styled.i`
margin-right: 0.25rem;
`;
const ToastButton: FC<Props> = ({ icon, onClick, children }) => { const ToastButton: FC<Props> = ({ icon, onClick, children }) => {
const theme = useContext(ToastThemeContext); const theme = useContext(ToastThemeContext);
@@ -61,7 +58,7 @@ const ToastButton: FC<Props> = ({ icon, onClick, children }) => {
return ( return (
<ThemedButton theme={theme} onClick={handleClick}> <ThemedButton theme={theme} onClick={handleClick}>
{icon && <ToastButtonIcon className={`fas fa-fw fa-${icon}`} />} {children} {icon && <i className={classNames("fas", "fa-fw", `fa-${icon}`, "mr-1")} />} {children}
</ThemedButton> </ThemedButton>
); );
}; };

View File

@@ -24,9 +24,7 @@
import React, { FC } from "react"; import React, { FC } from "react";
import styled from "styled-components"; import styled from "styled-components";
const Buttons = styled.div` const FullWidthDiv = styled.div`
display: flex;
padding-top: 0.5rem;
width: 100%; width: 100%;
& > * { & > * {
@@ -38,6 +36,6 @@ const Buttons = styled.div`
} }
`; `;
const ToastButtons: FC = ({ children }) => <Buttons>{children}</Buttons>; const ToastButtons: FC = ({ children }) => <FullWidthDiv className="is-flex pt-2">{children}</FullWidthDiv>;
export default ToastButtons; export default ToastButtons;

View File

@@ -21,10 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import { getTheme, Themeable, ToastThemeContext, Type } from "./themes";
import styled from "styled-components";
import React, { FC } from "react"; import React, { FC } from "react";
import classNames from "classnames";
import styled from "styled-components";
import { getTheme, Themeable, ToastThemeContext, Type } from "./themes";
type Props = { type Props = {
type: Type; type: Type;
@@ -33,31 +33,22 @@ type Props = {
}; };
const Container = styled.div<Themeable>` const Container = styled.div<Themeable>`
color: ${props => props.theme.primary}; color: ${(props) => props.theme.primary};
background-color: ${props => props.theme.secondary}; background-color: ${(props) => props.theme.secondary};
max-width: 18rem; max-width: 18rem;
font-size: 0.75rem;
border-radius: 5px; border-radius: 5px;
padding: 1.5rem;
margin-top: 0.5rem;
margin-bottom: 0 !important;
& > p { & > p {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
`; `;
const Title = styled.h1<Themeable>`
margin-bottom: 0.25rem;
font-weight: bold;
`;
const ToastNotification: FC<Props> = ({ children, title, type, close }) => { const ToastNotification: FC<Props> = ({ children, title, type, close }) => {
const theme = getTheme(type); const theme = getTheme(type);
return ( return (
<Container className="notification" theme={theme}> <Container className={classNames("notification", "mt-2", "mb-0", "p-5", "is-size-7")} theme={theme}>
{ close ? <button className="delete" onClick={close} /> : null } {close ? <button className="delete" onClick={close} /> : null}
<Title theme={theme}>{title}</Title> <h1 className={classNames("mb-1", "has-text-weight-bold")}>{title}</h1>
<ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider> <ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider>
</Container> </Container>
); );

View File

@@ -110,6 +110,7 @@ footer.footer {
// 6. Import the rest of Bulma // 6. Import the rest of Bulma
@import "bulma/bulma"; @import "bulma/bulma";
@import "bulma-tooltip/dist/css/bulma-tooltip.min"; @import "bulma-tooltip/dist/css/bulma-tooltip.min";
@import "bulma-popover/css/bulma-popover";
$dark-75: scale-color($dark, $lightness: 25%); $dark-75: scale-color($dark, $lightness: 25%);
$dark-50: scale-color($dark, $lightness: 50%); $dark-50: scale-color($dark, $lightness: 50%);
@@ -194,7 +195,10 @@ $light-25: darken($high-contrast-light-gray, 45%);
} }
.has-text-blue-light { .has-text-blue-light {
color: $blue-light !important; color: $blue-light;
}
.has-background-blue-light {
background-color: $blue-light;
} }
.has-text-high-contrast-warning { .has-text-high-contrast-warning {
@@ -286,10 +290,6 @@ $light-25: darken($high-contrast-light-gray, 45%);
background-color: $high-contrast-danger-25; background-color: $high-contrast-danger-25;
} }
.has-background-blue-light {
background-color: $blue-light;
}
.has-background-high-contrast-light-gray { .has-background-high-contrast-light-gray {
background-color: $high-contrast-light-gray; background-color: $high-contrast-light-gray;
} }
@@ -312,7 +312,7 @@ $light-25: darken($high-contrast-light-gray, 45%);
background-color: $light; background-color: $light;
} }
+.is-delete { + .is-delete {
border-color: #aaa; border-color: #aaa;
border-width: 1px 1px 1px 0; border-width: 1px 1px 1px 0;
} }
@@ -520,7 +520,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
color: #666; color: #666;
} }
.has-border-white { .has-border-white {
border-color: #fff !important; border-color: $white !important;
} }
ul.is-separated { ul.is-separated {
> li:after { > li:after {
@@ -603,7 +603,7 @@ ul.is-separated {
width: 100%; width: 100%;
td, td,
th { th {
border-color: #eee; border-color: $white-ter;
padding: 1rem; padding: 1rem;
} }
} }
@@ -805,7 +805,6 @@ form .field:not(.is-grouped) {
.menu { .menu {
div { div {
height: 100%; height: 100%;
/*border: 1px solid #eee;*/
margin-bottom: 1rem; margin-bottom: 1rem;
} }
} }
@@ -816,7 +815,7 @@ form .field:not(.is-grouped) {
color: #fff; color: #fff;
font-size: 1em; font-size: 1em;
font-weight: 600; font-weight: 600;
background-color: #bbb; background-color: $blue;
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
text-transform: none; text-transform: none;
@@ -826,9 +825,6 @@ form .field:not(.is-grouped) {
margin-bottom: 0; margin-bottom: 0;
} }
} }
.menu div:first-child .menu-label {
background-color: $blue;
}
.menu-list { .menu-list {
a { a {
color: #333; color: #333;
@@ -843,7 +839,7 @@ form .field:not(.is-grouped) {
> li { > li {
ul { ul {
margin: 0; margin: 0;
border-top: 1px solid #eee; border-top: 1px solid $white-ter;
li { li {
border-right: none; border-right: none;
@@ -866,15 +862,15 @@ form .field:not(.is-grouped) {
} }
border-radius: 0; border-radius: 0;
border-top: 1px solid #eee; border-top: 1px solid $white-ter;
border-left: 1px solid #eee; border-left: 1px solid $white-ter;
border-right: 1px solid #eee; border-right: 1px solid $white-ter;
} }
> li:first-child { > li:first-child {
border-top: none; border-top: none;
} }
li:last-child { li:last-child {
border-bottom: 1px solid #eee; border-bottom: 1px solid $white-ter;
} }
div { div {
margin-bottom: 0; margin-bottom: 0;
@@ -921,7 +917,7 @@ form .field:not(.is-grouped) {
margin-left: 0; margin-left: 0;
} }
// cursor // NOTE: use .is-clickable instead!
.has-cursor-pointer { .has-cursor-pointer {
cursor: pointer; cursor: pointer;
} }
@@ -952,5 +948,3 @@ form .field:not(.is-grouped) {
@include loader; @include loader;
} }
} }
@import "bulma-popover/css/bulma-popover";

View File

@@ -27,18 +27,10 @@ import styled from "styled-components";
import { ErrorNotification, Image, Loading, Subtitle, Title } from "@scm-manager/ui-components"; import { ErrorNotification, Image, Loading, Subtitle, Title } from "@scm-manager/ui-components";
import { useUpdateInfo, useVersion } from "@scm-manager/ui-api"; import { useUpdateInfo, useVersion } from "@scm-manager/ui-api";
const BottomMarginDiv = styled.div`
margin-bottom: 1.5rem;
`;
const BoxShadowBox = styled.div` const BoxShadowBox = styled.div`
box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1), 0 0 0 2px rgba(40, 177, 232, 0.2); box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1), 0 0 0 2px rgba(40, 177, 232, 0.2);
`; `;
const NoBottomMarginSubtitle = styled(Subtitle)`
margin-bottom: 0.25rem !important;
`;
const ImageWrapper = styled.div` const ImageWrapper = styled.div`
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
`; `;
@@ -68,7 +60,7 @@ const AdminDetails: FC = () => {
<h3 className="has-text-weight-medium">{t("admin.info.newRelease.title")}</h3> <h3 className="has-text-weight-medium">{t("admin.info.newRelease.title")}</h3>
<p> <p>
{t("admin.info.newRelease.description", { {t("admin.info.newRelease.description", {
version: updateInfo?.latestVersion version: updateInfo?.latestVersion,
})} })}
</p> </p>
<a className="button is-warning is-pulled-right" target="_blank" href={updateInfo?.link}> <a className="button is-warning is-pulled-right" target="_blank" href={updateInfo?.link}>
@@ -85,8 +77,8 @@ const AdminDetails: FC = () => {
return ( return (
<> <>
<Title title={t("admin.info.title")} /> <Title title={t("admin.info.title")} />
<NoBottomMarginSubtitle subtitle={t("admin.info.currentAppVersion")} /> <Subtitle className="mb-1" subtitle={t("admin.info.currentAppVersion")} />
<BottomMarginDiv>{version}</BottomMarginDiv> <div className="mb-5">{version}</div>
{updateInfo ? renderUpdateInfo() : null} {updateInfo ? renderUpdateInfo() : null}
<BoxShadowBox className="box"> <BoxShadowBox className="box">
<article className="media"> <article className="media">

View File

@@ -22,6 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
import * as React from "react"; import * as React from "react";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
type Props = { type Props = {
@@ -29,15 +30,16 @@ type Props = {
}; };
const ActionWrapper = styled.div` const ActionWrapper = styled.div`
justify-content: center; border: 2px solid #e9f7fd;
margin-top: 2em;
padding: 1em 1em;
border: 2px solid #e9f7df;
`; `;
export default class PluginBottomActions extends React.Component<Props> { export default class PluginBottomActions extends React.Component<Props> {
render() { render() {
const { children } = this.props; const { children } = this.props;
return <ActionWrapper className="is-flex">{children}</ActionWrapper>; return (
<ActionWrapper className={classNames("is-flex", "is-justify-content-center", "mt-5", "p-4")}>
{children}
</ActionWrapper>
);
} }
} }

View File

@@ -40,13 +40,11 @@ const ActionbarWrapper = styled.div`
} }
`; `;
const IconWrapper = styled.span` const IconWrapper = styled.span.attrs((props) => ({
margin-bottom: 0 !important; className: "level-item mb-0 p-2 is-clickable",
padding: 0.5rem; }))`
border: 1px solid #cdcdcd; // $dark-25 border: 1px solid #cdcdcd; // $dark-25
border-radius: 4px; border-radius: 4px;
cursor: pointer;
pointer-events: all;
&:hover { &:hover {
border-color: #9a9a9a; // $dark-50 border-color: #9a9a9a; // $dark-50
@@ -78,22 +76,22 @@ const PluginEntry: FC<Props> = ({ plugin, openModal }) => {
const actionBar = () => ( const actionBar = () => (
<ActionbarWrapper className="is-flex"> <ActionbarWrapper className="is-flex">
{isCloudoguPlugin && ( {isCloudoguPlugin && (
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.CLOUDOGU })}> <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.CLOUDOGU })}>
<Icon title={t("plugins.modal.cloudoguInstall")} name="link" color="success-dark" /> <Icon title={t("plugins.modal.cloudoguInstall")} name="link" color="success-dark" />
</IconWrapper> </IconWrapper>
)} )}
{isInstallable && ( {isInstallable && (
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.INSTALL })}> <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.INSTALL })}>
<Icon title={t("plugins.modal.install")} name="download" color="info" /> <Icon title={t("plugins.modal.install")} name="download" color="info" />
</IconWrapper> </IconWrapper>
)} )}
{isUninstallable && ( {isUninstallable && (
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.UNINSTALL })}> <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.UNINSTALL })}>
<Icon title={t("plugins.modal.uninstall")} name="trash" color="info" /> <Icon title={t("plugins.modal.uninstall")} name="trash" color="info" />
</IconWrapper> </IconWrapper>
)} )}
{isUpdatable && ( {isUpdatable && (
<IconWrapper className="level-item" onClick={() => openModal({ plugin, action: PluginAction.UPDATE })}> <IconWrapper onClick={() => openModal({ plugin, action: PluginAction.UPDATE })}>
<Icon title={t("plugins.modal.update")} name="sync-alt" color="info" /> <Icon title={t("plugins.modal.update")} name="sync-alt" color="info" />
</IconWrapper> </IconWrapper>
)} )}

View File

@@ -41,10 +41,10 @@ type ParentWithPluginAction = {
pluginAction?: PluginAction; pluginAction?: PluginAction;
}; };
const ListParent = styled.div.attrs((props) => ({}))<ParentWithPluginAction>` const ListParent = styled.div.attrs((props) => ({
margin-right: 0; className: "field-label is-inline-flex mr-0 has-text-left",
}))<ParentWithPluginAction>`
min-width: ${(props) => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")}; min-width: ${(props) => (props.pluginAction === PluginAction.INSTALL ? "5.5em" : "10em")};
text-align: left;
`; `;
const ListChild = styled.div` const ListChild = styled.div`
@@ -194,9 +194,7 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
<div className="media"> <div className="media">
<div className="media-content"> <div className="media-content">
<div className="field is-horizontal"> <div className="field is-horizontal">
<ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}> <ListParent pluginAction={pluginAction}>{t("plugins.modal.author")}:</ListParent>
{t("plugins.modal.author")}:
</ListParent>
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild> <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.author}</ListChild>
</div> </div>
{pluginAction === PluginAction.CLOUDOGU && ( {pluginAction === PluginAction.CLOUDOGU && (
@@ -208,25 +206,19 @@ const PluginModal: FC<Props> = ({ onClose, pluginAction, plugin }) => {
)} )}
{pluginAction === PluginAction.INSTALL && ( {pluginAction === PluginAction.INSTALL && (
<div className="field is-horizontal"> <div className="field is-horizontal">
<ListParent className={classNames("field-label", "is-inline-flex")} pluginAction={pluginAction}> <ListParent pluginAction={pluginAction}>{t("plugins.modal.version")}:</ListParent>
{t("plugins.modal.version")}:
</ListParent>
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild> <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild>
</div> </div>
)} )}
{(pluginAction === PluginAction.UPDATE || pluginAction === PluginAction.UNINSTALL) && ( {(pluginAction === PluginAction.UPDATE || pluginAction === PluginAction.UNINSTALL) && (
<div className="field is-horizontal"> <div className="field is-horizontal">
<ListParent className={classNames("field-label", "is-inline-flex")}> <ListParent>{t("plugins.modal.currentVersion")}:</ListParent>
{t("plugins.modal.currentVersion")}:
</ListParent>
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild> <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.version}</ListChild>
</div> </div>
)} )}
{pluginAction === PluginAction.UPDATE && ( {pluginAction === PluginAction.UPDATE && (
<div className="field is-horizontal"> <div className="field is-horizontal">
<ListParent className={classNames("field-label", "is-inline-flex")}> <ListParent>{t("plugins.modal.newVersion")}:</ListParent>
{t("plugins.modal.newVersion")}:
</ListParent>
<ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.newVersion}</ListChild> <ListChild className={classNames("field-body", "is-inline-flex")}>{plugin.newVersion}</ListChild>
</div> </div>
)} )}

View File

@@ -23,24 +23,27 @@
*/ */
import * as React from "react"; import * as React from "react";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components";
type Props = { type Props = {
children?: React.Node; children?: React.Node;
}; };
const ChildWrapper = styled.div`
justify-content: flex-end;
align-items: center;
`;
export default class PluginTopActions extends React.Component<Props> { export default class PluginTopActions extends React.Component<Props> {
render() { render() {
const { children } = this.props; const { children } = this.props;
return ( return (
<ChildWrapper className={classNames("column", "is-flex", "is-one-fifths", "is-mobile-action-spacing")}> <div
className={classNames(
"column",
"is-one-fifths",
"is-mobile-action-spacing",
"is-flex",
"is-justify-content-flex-end",
"is-align-items-center"
)}
>
{children} {children}
</ChildWrapper> </div>
); );
} }
} }

View File

@@ -36,7 +36,7 @@ class AvailableVerbs extends React.Component<Props> {
let verbs = null; let verbs = null;
if (role.verbs.length > 0) { if (role.verbs.length > 0) {
verbs = ( verbs = (
<td className="is-paddingless"> <td className="p-0">
<ul> <ul>
{role.verbs.map((verb, key) => { {role.verbs.map((verb, key) => {
return <li key={key}>{t("verbs.repository." + verb + ".displayName")}</li>; return <li key={key}>{t("verbs.repository." + verb + ".displayName")}</li>;

View File

@@ -23,24 +23,18 @@
*/ */
import React from "react"; import React from "react";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import styled from "styled-components";
import { Tag } from "@scm-manager/ui-components"; import { Tag } from "@scm-manager/ui-components";
type Props = WithTranslation & { type Props = WithTranslation & {
system?: boolean; system?: boolean;
}; };
const LeftMarginTag = styled(Tag)`
margin-left: 0.75rem;
vertical-align: inherit;
`;
class SystemRoleTag extends React.Component<Props> { class SystemRoleTag extends React.Component<Props> {
render() { render() {
const { system, t } = this.props; const { system, t } = this.props;
if (system) { if (system) {
return <LeftMarginTag color="dark" label={t("repositoryRole.system")} />; return <Tag className="ml-3" color="dark" label={t("repositoryRole.system")} />;
} }
return null; return null;
} }

View File

@@ -33,28 +33,13 @@ type Props = WithTranslation & {
item?: InfoItem; item?: InfoItem;
}; };
const BottomMarginA = styled.a`
display: blocK;
margin-bottom: 1.5rem;
`;
const FixedSizedIconWrapper = styled.div` const FixedSizedIconWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 160px; width: 160px;
height: 160px; height: 160px;
`; `;
const LightBlueIcon = styled(Icon)`
margin-bottom: 0.5em;
color: #bff1e6;
`;
const ContentWrapper = styled.div` const ContentWrapper = styled.div`
min-height: 10.5rem; min-height: 10.5rem;
margin-left: 1.5em;
`; `;
class InfoBox extends React.Component<Props> { class InfoBox extends React.Component<Props> {
@@ -64,7 +49,7 @@ class InfoBox extends React.Component<Props> {
const summary = item ? item.summary : t("login.loading"); const summary = item ? item.summary : t("login.loading");
return ( return (
<ContentWrapper className={classNames("media-content", "content")}> <ContentWrapper className={classNames("media-content", "content", "ml-5")}>
<h4 className="has-text-link">{title}</h4> <h4 className="has-text-link">{title}</h4>
<p>{summary}</p> <p>{summary}</p>
</ContentWrapper> </ContentWrapper>
@@ -75,20 +60,30 @@ class InfoBox extends React.Component<Props> {
const { item, type, t } = this.props; const { item, type, t } = this.props;
const icon = type === "plugin" ? "puzzle-piece" : "star"; const icon = type === "plugin" ? "puzzle-piece" : "star";
return ( return (
<BottomMarginA href={item._links.self.href}> <a className="is-block mb-5" href={item._links.self.href}>
<div className="box media"> <div className="box media">
<figure className="media-left"> <figure className="media-left">
<FixedSizedIconWrapper <FixedSizedIconWrapper
className={classNames("image", "box", "has-text-weight-bold", "has-text-white", "has-background-info")} className={classNames(
"image",
"box",
"has-text-weight-bold",
"has-text-white",
"has-background-info",
"is-flex",
"is-flex-direction-column",
"is-justify-content-center",
"is-align-items-center"
)}
> >
<LightBlueIcon className="fa-2x" name={icon} color="inherit" /> <Icon className="has-text-blue-light mb-2 fa-2x" name={icon} color="inherit" />
<div className="is-size-4">{t("login." + type)}</div> <div className="is-size-4">{t("login." + type)}</div>
<div className="is-size-4">{t("login.tip")}</div> <div className="is-size-4">{t("login.tip")}</div>
</FixedSizedIconWrapper> </FixedSizedIconWrapper>
</figure> </figure>
{this.renderBody()} {this.renderBody()}
</div> </div>
</BottomMarginA> </a>
); );
} }
} }

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, {FormEvent} from "react"; import React, { FormEvent } from "react";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import styled from "styled-components"; import styled from "styled-components";
import { ErrorNotification, Image, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components"; import { ErrorNotification, Image, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components";
@@ -50,7 +50,7 @@ const AvatarImage = styled(Image)`
width: 128px; width: 128px;
height: 128px; height: 128px;
padding: 5px; padding: 5px;
background: #fff; background: white;
border: 1px solid lightgray; border: 1px solid lightgray;
border-radius: 50%; border-radius: 50%;
`; `;
@@ -60,7 +60,7 @@ class LoginForm extends React.Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
username: "", username: "",
password: "" password: "",
}; };
} }
@@ -73,13 +73,13 @@ class LoginForm extends React.Component<Props, State> {
handleUsernameChange = (value: string) => { handleUsernameChange = (value: string) => {
this.setState({ this.setState({
username: value username: value,
}); });
}; };
handlePasswordChange = (value: string) => { handlePasswordChange = (value: string) => {
this.setState({ this.setState({
password: value password: value,
}); });
}; };

View File

@@ -114,7 +114,7 @@ class LoginInfo extends React.Component<Props, State> {
createInfoPanel = (info: LoginInfoResponse) => ( createInfoPanel = (info: LoginInfoResponse) => (
<NoOpErrorBoundary> <NoOpErrorBoundary>
<div className="column is-7 is-offset-1 is-paddingless"> <div className="column is-7 is-offset-1 p-0">
<InfoBox item={info.feature} type="feature" /> <InfoBox item={info.feature} type="feature" />
<InfoBox item={info.plugin} type="plugin" /> <InfoBox item={info.plugin} type="plugin" />
</div> </div>

View File

@@ -131,12 +131,12 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => {
<h3 className="title">{t("title")}</h3> <h3 className="title">{t("title")}</h3>
<h4 className="subtitle">{t("adminStep.title")}</h4> <h4 className="subtitle">{t("adminStep.title")}</h4>
<p>{t("adminStep.description")}</p> <p>{t("adminStep.description")}</p>
<div className={"columns"}> <div className="columns">
<div className="column is-full-width"> <div className="column is-full-width">
<InputField placeholder={t("adminStep.startupToken")} autofocus={true} {...register("startupToken")} /> <InputField placeholder={t("adminStep.startupToken")} autofocus={true} {...register("startupToken")} />
</div> </div>
</div> </div>
<div className={"columns"}> <div className="columns">
<div className="column is-half"> <div className="column is-half">
<InputField <InputField
testId="username-input" testId="username-input"
@@ -154,7 +154,7 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => {
/> />
</div> </div>
</div> </div>
<div className={"columns"}> <div className="columns">
<div className="column is-full-width"> <div className="column is-full-width">
<InputField <InputField
label={t("adminStep.email")} label={t("adminStep.email")}
@@ -163,7 +163,7 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => {
/> />
</div> </div>
</div> </div>
<div className={"columns"}> <div className="columns">
<div className="column is-half"> <div className="column is-half">
<InputField <InputField
testId="password-input" testId="password-input"
@@ -184,7 +184,7 @@ const InitializationAdminAccountStep: FC<Props> = ({ data }) => {
</div> </div>
</div> </div>
{errorComponent} {errorComponent}
<div className={"columns"}> <div className="columns">
<div className="column is-full-width"> <div className="column is-full-width">
<SubmitButton <SubmitButton
label={t("adminStep.submit")} label={t("adminStep.submit")}

View File

@@ -23,15 +23,10 @@
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import { Redirect, useLocation } from "react-router-dom"; import { Redirect, useLocation } from "react-router-dom";
import styled from "styled-components";
import LoginInfo from "../components/LoginInfo"; import LoginInfo from "../components/LoginInfo";
import { parse } from "query-string"; import { parse } from "query-string";
import { useIndexLink, useLogin } from "@scm-manager/ui-api"; import { useIndexLink, useLogin } from "@scm-manager/ui-api";
const HeroSection = styled.section`
padding-top: 2em;
`;
interface FromObject { interface FromObject {
from?: string; from?: string;
} }
@@ -62,7 +57,7 @@ const Login: FC = () => {
} }
return ( return (
<HeroSection className="hero"> <section className="hero pt-6">
<div className="hero-body"> <div className="hero-body">
<div className="container"> <div className="container">
<div className="columns is-centered"> <div className="columns is-centered">
@@ -70,7 +65,7 @@ const Login: FC = () => {
</div> </div>
</div> </div>
</div> </div>
</HeroSection> </section>
); );
}; };

View File

@@ -21,8 +21,18 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useEffect, useState } from "react"; import React, { FC, useEffect, useState } from "react";
import { useHistory, Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import {
useClearNotifications,
useDismissNotification,
useNotifications,
useNotificationSubscription,
} from "@scm-manager/ui-api";
import { Notification, NotificationCollection } from "@scm-manager/ui-types";
import { import {
Button, Button,
Notification as InfoNotification, Notification as InfoNotification,
@@ -35,26 +45,6 @@ import {
DateFromNow, DateFromNow,
devices, devices,
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import styled from "styled-components";
import {
useClearNotifications,
useDismissNotification,
useNotifications,
useNotificationSubscription,
} from "@scm-manager/ui-api";
import { Notification, NotificationCollection } from "@scm-manager/ui-types";
import { useHistory, Link } from "react-router-dom";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
const Bell = styled(Icon)`
font-size: 1.5rem;
`;
const Container = styled.div`
display: flex;
cursor: pointer;
`;
const DropDownMenu = styled.div` const DropDownMenu = styled.div`
min-width: 35rem; min-width: 35rem;
@@ -130,7 +120,7 @@ const NotificationEntry: FC<EntryProps> = ({ notification, removeToast }) => {
} }
return ( return (
<tr className={`is-${color(notification)}`}> <tr className={`is-${color(notification)}`}>
<VerticalCenteredTd onClick={() => history.push(notification.link)} className="has-cursor-pointer"> <VerticalCenteredTd onClick={() => history.push(notification.link)} className="is-clickable">
<NotificationMessage message={notification.message} /> <NotificationMessage message={notification.message} />
</VerticalCenteredTd> </VerticalCenteredTd>
<DateColumn className="has-text-right"> <DateColumn className="has-text-right">
@@ -143,7 +133,7 @@ const NotificationEntry: FC<EntryProps> = ({ notification, removeToast }) => {
<Icon <Icon
name="trash" name="trash"
color="black" color="black"
className="has-cursor-pointer" className="is-clickable"
title={t("notifications.dismiss")} title={t("notifications.dismiss")}
onClick={remove} onClick={remove}
/> />
@@ -172,7 +162,7 @@ const ClearEntry: FC<ClearEntryProps> = ({ notifications, clearToasts }) => {
clearStore(); clearStore();
}; };
return ( return (
<div className="dropdown-item has-text-centered"> <div className={classNames("dropdown-item", "has-text-centered")}>
<ErrorNotification error={error} /> <ErrorNotification error={error} />
<DismissAllButton className="is-outlined" color="link" loading={isLoading} action={clear}> <DismissAllButton className="is-outlined" color="link" loading={isLoading} action={clear}>
<Icon color="link" name="trash" className="mr-1" /> {t("notifications.dismissAll")} <Icon color="link" name="trash" className="mr-1" /> {t("notifications.dismissAll")}
@@ -189,8 +179,8 @@ const NotificationList: FC<Props> = ({ data, clear, remove }) => {
const top = all.slice(0, 6); const top = all.slice(0, 6);
return ( return (
<div className="dropdown-content p-0"> <div className={classNames("dropdown-content", "p-0")}>
<table className="table mb-0 card-table"> <table className={classNames("table", "card-table", "mb-0")}>
<tbody> <tbody>
{top.map((n, i) => ( {top.map((n, i) => (
<NotificationEntry key={i} notification={n} removeToast={remove} /> <NotificationEntry key={i} notification={n} removeToast={remove} />
@@ -198,14 +188,18 @@ const NotificationList: FC<Props> = ({ data, clear, remove }) => {
</tbody> </tbody>
</table> </table>
{all.length > 6 ? ( {all.length > 6 ? (
<p className="has-text-centered has-text-grey">{t("notifications.xMore", { count: all.length - 6 })}</p> <p className={classNames("has-text-centered", "has-text-grey")}>
{t("notifications.xMore", { count: all.length - 6 })}
</p>
) : null} ) : null}
{clearLink ? <ClearEntry notifications={data} clearToasts={clear} /> : null} {clearLink ? <ClearEntry notifications={data} clearToasts={clear} /> : null}
</div> </div>
); );
}; };
const DropdownMenuContainer: FC = ({ children }) => <div className="dropdown-content p-4">{children}</div>; const DropdownMenuContainer: FC = ({ children }) => (
<div className={classNames("dropdown-content", "p-4")}>{children}</div>
);
const NoNotifications: FC = () => { const NoNotifications: FC = () => {
const [t] = useTranslation("commons"); const [t] = useTranslation("commons");
@@ -278,12 +272,8 @@ const NotificationSubscription: FC<SubscriptionProps> = ({ notifications, remove
}; };
const BellNotificationContainer = styled.div` const BellNotificationContainer = styled.div`
position: relative;
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
display: flex;
align-items: center;
justify-content: center;
`; `;
type NotificationCounterProps = { type NotificationCounterProps = {
@@ -304,8 +294,11 @@ type BellNotificationIconProps = {
const BellNotificationIcon: FC<BellNotificationIconProps> = ({ data, onClick }) => { const BellNotificationIcon: FC<BellNotificationIconProps> = ({ data, onClick }) => {
const counter = data?._embedded.notifications.length || 0; const counter = data?._embedded.notifications.length || 0;
return ( return (
<BellNotificationContainer onClick={onClick}> <BellNotificationContainer
<Bell iconStyle={counter === 0 ? "far" : "fas"} name="bell" color="white" /> className={classNames("is-relative", "is-flex", "is-justify-content-center", "is-align-items-center")}
onClick={onClick}
>
<Icon className="is-size-4" iconStyle={counter === 0 ? "far" : "fas"} name="bell" color="white" />
{counter > 0 ? <NotificationCounter count={counter}>{counter < 100 ? counter : "∞"}</NotificationCounter> : null} {counter > 0 ? <NotificationCounter count={counter}>{counter < 100 ? counter : "∞"}</NotificationCounter> : null}
</BellNotificationContainer> </BellNotificationContainer>
); );
@@ -357,9 +350,9 @@ const Notifications: FC<NotificationProps> = ({ className }) => {
)} )}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<Container className="dropdown-trigger"> <div className={classNames("is-flex", "dropdown-trigger", "is-clickable")}>
<BellNotificationIcon data={data} onClick={() => setOpen((o) => !o)} /> <BellNotificationIcon data={data} onClick={() => setOpen((o) => !o)} />
</Container> </div>
<DropDownMenu className="dropdown-menu" id="dropdown-menu" role="menu"> <DropDownMenu className="dropdown-menu" id="dropdown-menu" role="menu">
<ErrorBox error={error} /> <ErrorBox error={error} />
{isLoading ? <LoadingBox /> : null} {isLoading ? <LoadingBox /> : null}

View File

@@ -34,10 +34,6 @@ import SyntaxModal from "../search/SyntaxModal";
import SearchErrorNotification from "../search/SearchErrorNotification"; import SearchErrorNotification from "../search/SearchErrorNotification";
import queryString from "query-string"; import queryString from "query-string";
const Field = styled.div`
margin-bottom: 0 !important;
`;
const Input = styled.input` const Input = styled.input`
border-radius: 4px !important; border-radius: 4px !important;
`; `;
@@ -77,12 +73,6 @@ const EmptyHits: FC = () => {
const ResultHeading = styled.h3` const ResultHeading = styled.h3`
border-bottom: 1px solid lightgray; border-bottom: 1px solid lightgray;
margin: 0 0.5rem;
padding: 0.375rem 0.5rem;
font-weight: bold;
display: flex;
align-items: center;
justify-content: space-between;
`; `;
const DropdownMenu = styled.div` const DropdownMenu = styled.div`
@@ -91,8 +81,6 @@ const DropdownMenu = styled.div`
const ResultFooter = styled.div` const ResultFooter = styled.div`
border-top: 1px solid lightgray; border-top: 1px solid lightgray;
margin: 0 0.5rem;
padding: 0.375rem 0.5rem;
`; `;
const AvatarSection: FC<HitProps> = ({ hit }) => { const AvatarSection: FC<HitProps> = ({ hit }) => {
@@ -115,7 +103,7 @@ const AvatarSection: FC<HitProps> = ({ hit }) => {
const MoreResults: FC<GotoProps> = ({ gotoDetailSearch }) => { const MoreResults: FC<GotoProps> = ({ gotoDetailSearch }) => {
const [t] = useTranslation("commons"); const [t] = useTranslation("commons");
return ( return (
<ResultFooter className="dropdown-item has-text-centered"> <ResultFooter className={classNames("dropdown-item", "has-text-centered", "mx-2", "px-2", "py-1")}>
<Button action={gotoDetailSearch} color="primary" data-omnisearch="true"> <Button action={gotoDetailSearch} color="primary" data-omnisearch="true">
{t("search.quickSearch.moreResults")} {t("search.quickSearch.moreResults")}
</Button> </Button>
@@ -156,7 +144,18 @@ const Hits: FC<HitsProps> = ({ showHelp, gotoDetailSearch, ...rest }) => {
return ( return (
<> <>
<div aria-expanded="true" role="listbox" className="dropdown-content"> <div aria-expanded="true" role="listbox" className="dropdown-content">
<ResultHeading className="dropdown-item"> <ResultHeading
className={classNames(
"dropdown-item",
"is-flex",
"is-justify-content-space-between",
"is-align-items-center",
"mx-2",
"px-2",
"py-1",
"has-text-weight-bold"
)}
>
<span>{t("search.quickSearch.resultHeading")}</span> <span>{t("search.quickSearch.resultHeading")}</span>
<SyntaxHelp onClick={showHelp} /> <SyntaxHelp onClick={showHelp} />
</ResultHeading> </ResultHeading>
@@ -331,7 +330,7 @@ const OmniSearch: FC = () => {
const { onKeyDown, index } = useKeyBoardNavigation(gotoDetailSearch, clearQuery, data?._embedded.hits); const { onKeyDown, index } = useKeyBoardNavigation(gotoDetailSearch, clearQuery, data?._embedded.hits);
return ( return (
<Field className="navbar-item field"> <div className={classNames("navbar-item", "field", "mb-0")}>
{showHelp ? <SyntaxModal close={closeHelp} /> : null} {showHelp ? <SyntaxModal close={closeHelp} /> : null}
<div <div
className={classNames("control", "has-icons-right", { className={classNames("control", "has-icons-right", {
@@ -376,7 +375,7 @@ const OmniSearch: FC = () => {
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
</Field> </div>
); );
}; };

View File

@@ -22,9 +22,10 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import classNames from "classnames";
import styled from "styled-components";
import { apiClient, Loading, ErrorNotification, ErrorBoundary, Icon } from "@scm-manager/ui-components"; import { apiClient, Loading, ErrorNotification, ErrorBoundary, Icon } from "@scm-manager/ui-components";
import loadBundle from "./loadBundle"; import loadBundle from "./loadBundle";
import styled from "styled-components";
type Props = { type Props = {
loaded: boolean; loaded: boolean;
@@ -48,23 +49,11 @@ const BigIcon = styled(Icon)`
font-size: 10rem; font-size: 10rem;
`; `;
const Centered = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
`;
const ErrorMessage = styled.span`
font-size: 20px;
margin: 1.5rem 0;
`;
class PluginLoader extends React.Component<Props, State> { class PluginLoader extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
message: "booting" message: "booting",
}; };
} }
@@ -72,7 +61,7 @@ class PluginLoader extends React.Component<Props, State> {
const { loaded } = this.props; const { loaded } = this.props;
if (!loaded) { if (!loaded) {
this.setState({ this.setState({
message: "loading plugin information" message: "loading plugin information",
}); });
this.getPlugins(this.props.link); this.getPlugins(this.props.link);
@@ -82,16 +71,16 @@ class PluginLoader extends React.Component<Props, State> {
getPlugins = (link: string): Promise<any> => { getPlugins = (link: string): Promise<any> => {
return apiClient return apiClient
.get(link) .get(link)
.then(response => response.text()) .then((response) => response.text())
.then(JSON.parse) .then(JSON.parse)
.then(pluginCollection => pluginCollection._embedded.plugins) .then((pluginCollection) => pluginCollection._embedded.plugins)
.then(this.loadPlugins) .then(this.loadPlugins)
.then(this.props.callback); .then(this.props.callback);
}; };
loadPlugins = (plugins: Plugin[]) => { loadPlugins = (plugins: Plugin[]) => {
this.setState({ this.setState({
message: "loading plugins" message: "loading plugins",
}); });
const promises = []; const promises = [];
@@ -100,21 +89,21 @@ class PluginLoader extends React.Component<Props, State> {
promises.push(this.loadPlugin(plugin)); promises.push(this.loadPlugin(plugin));
} }
return promises.reduce((chain, current) => { return promises.reduce((chain, current) => {
return chain.then(chainResults => { return chain.then((chainResults) => {
return current.then(currentResult => [...chainResults, currentResult]); return current.then((currentResult) => [...chainResults, currentResult]);
}); });
}, Promise.resolve([])); }, Promise.resolve([]));
}; };
loadPlugin = (plugin: Plugin) => { loadPlugin = (plugin: Plugin) => {
this.setState({ this.setState({
message: `loading ${plugin.name}` message: `loading ${plugin.name}`,
}); });
const promises = []; const promises = [];
for (const bundle of plugin.bundles) { for (const bundle of plugin.bundles) {
promises.push( promises.push(
loadBundle(bundle).catch(error => this.setState({ error, errorMessage: `loading ${plugin.name} failed` })) loadBundle(bundle).catch((error) => this.setState({ error, errorMessage: `loading ${plugin.name} failed` }))
); );
} }
return Promise.all(promises); return Promise.all(promises);
@@ -129,10 +118,17 @@ class PluginLoader extends React.Component<Props, State> {
<section className="section"> <section className="section">
<div className="container"> <div className="container">
<ErrorBoundary> <ErrorBoundary>
<Centered> <div
className={classNames(
"is-flex",
"is-flex-direction-column",
"is-justify-content-space-between",
"is-align-items-center"
)}
>
<BigIcon name="exclamation-triangle" color="danger" /> <BigIcon name="exclamation-triangle" color="danger" />
<ErrorMessage>{errorMessage}</ErrorMessage> <div className={classNames("my-5", "is-size-5")}>{errorMessage}</div>
</Centered> </div>
<ErrorNotification error={error} /> <ErrorNotification error={error} />
</ErrorBoundary> </ErrorBoundary>
</div> </div>

View File

@@ -80,7 +80,7 @@ class ProfileInfo extends React.Component<Props> {
groups = ( groups = (
<tr> <tr>
<th>{t("profile.groups")}</th> <th>{t("profile.groups")}</th>
<td className="is-paddingless"> <td className="p-0">
<ul> <ul>
{me.groups.map(group => { {me.groups.map(group => {
return <li>{group}</li>; return <li>{group}</li>;

View File

@@ -23,7 +23,6 @@
*/ */
import React from "react"; import React from "react";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import styled from "styled-components";
import { Group } from "@scm-manager/ui-types"; import { Group } from "@scm-manager/ui-types";
import { Checkbox, DateFromNow } from "@scm-manager/ui-components"; import { Checkbox, DateFromNow } from "@scm-manager/ui-components";
import GroupMember from "./GroupMember"; import GroupMember from "./GroupMember";
@@ -32,10 +31,6 @@ type Props = WithTranslation & {
group: Group; group: Group;
}; };
const StyledMemberList = styled.ul`
margin-left: 1em !important;
`;
class Details extends React.Component<Props> { class Details extends React.Component<Props> {
render() { render() {
const { group, t } = this.props; const { group, t } = this.props;
@@ -86,12 +81,12 @@ class Details extends React.Component<Props> {
member = ( member = (
<tr> <tr>
<th>{t("group.members")}</th> <th>{t("group.members")}</th>
<td className="is-paddingless"> <td className="p-0">
<StyledMemberList> <ul className="ml-4">
{group._embedded.members.map((member, index) => { {group._embedded.members.map((member, index) => {
return <GroupMember key={index} member={member} />; return <GroupMember key={index} member={member} />;
})} })}
</StyledMemberList> </ul>
</td> </td>
</tr> </tr>
); );

View File

@@ -23,6 +23,7 @@
*/ */
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
import { Member } from "@scm-manager/ui-types"; import { Member } from "@scm-manager/ui-types";
import { Icon } from "@scm-manager/ui-components"; import { Icon } from "@scm-manager/ui-components";
@@ -32,9 +33,6 @@ type Props = {
}; };
const StyledMember = styled.li` const StyledMember = styled.li`
display: inline-block;
margin-right: 0.25rem;
padding: 0.25rem 0.75rem;
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 4px; border-radius: 4px;
`; `;
@@ -59,6 +57,10 @@ export default class GroupMember extends React.Component<Props> {
render() { render() {
const { member } = this.props; const { member } = this.props;
const to = `/user/${member.name}`; const to = `/user/${member.name}`;
return <StyledMember>{this.showName(to, member)}</StyledMember>; return (
<StyledMember className={classNames("is-inline-block", "mr-1", "px-3", "py-1")}>
{this.showName(to, member)}
</StyledMember>
);
} }
} }

View File

@@ -24,8 +24,8 @@
import React from "react"; import React from "react";
import classNames from "classnames"; import classNames from "classnames";
import styled from "styled-components"; import styled from "styled-components";
import PermissionCheckbox from "./PermissionCheckbox";
import { Loading } from "@scm-manager/ui-components"; import { Loading } from "@scm-manager/ui-components";
import PermissionCheckbox from "./PermissionCheckbox";
type Props = { type Props = {
permissions: { permissions: {
@@ -37,8 +37,6 @@ type Props = {
}; };
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
padding-bottom: 0;
& .field .control { & .field .control {
width: 100%; width: 100%;
word-wrap: break-word; word-wrap: break-word;
@@ -56,7 +54,7 @@ export default class PermissionsWrapper extends React.Component<Props> {
const permissionArray = Object.keys(permissions); const permissionArray = Object.keys(permissions);
return ( return (
<div className="columns"> <div className="columns">
<StyledWrapper className={classNames("column", "is-half")}> <StyledWrapper className={classNames("column", "is-half", "pb-0")}>
{permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => ( {permissionArray.slice(0, permissionArray.length / 2 + 1).map(p => (
<PermissionCheckbox <PermissionCheckbox
key={p} key={p}

View File

@@ -22,49 +22,34 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React from "react"; import React from "react";
import { Branch, Repository } from "@scm-manager/ui-types";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import classNames from "classnames";
import { Branch, Repository } from "@scm-manager/ui-types";
import { DateFromNow } from "@scm-manager/ui-components";
import BranchButtonGroup from "./BranchButtonGroup"; import BranchButtonGroup from "./BranchButtonGroup";
import DefaultBranchTag from "./DefaultBranchTag"; import DefaultBranchTag from "./DefaultBranchTag";
import { DateFromNow } from "@scm-manager/ui-components";
import styled from "styled-components";
type Props = WithTranslation & { type Props = WithTranslation & {
repository: Repository; repository: Repository;
branch: Branch; branch: Branch;
}; };
const FlexRow = styled.div`
display: flex;
align-items: center;
flex-wrap: wrap;
`;
const Created = styled.div`
margin-left: 0.5rem;
font-size: 0.8rem;
`;
const Label = styled.strong`
margin-right: 0.3rem;
`;
const Date = styled(DateFromNow)`
font-size: 0.8rem;
`;
class BranchDetail extends React.Component<Props> { class BranchDetail extends React.Component<Props> {
render() { render() {
const { repository, branch, t } = this.props; const { repository, branch, t } = this.props;
return ( return (
<div className="media"> <div className="media">
<FlexRow className="media-content subtitle"> <div
<Label>{t("branch.name")}</Label> {branch.name} <DefaultBranchTag defaultBranch={branch.defaultBranch} /> className={classNames("media-content", "subtitle", "is-flex", "is-align-items-center", "is-flex-wrap-wrap")}
<Created className="is-ellipsis-overflow"> >
{t("branches.overview.lastCommit")} <Date date={branch.lastCommitDate} className="has-text-grey" /> <strong className="mr-1">{t("branch.name")}</strong> {branch.name}{" "}
</Created> <DefaultBranchTag defaultBranch={branch.defaultBranch} />
</FlexRow> <div className={classNames("is-ellipsis-overflow", "is-size-7", "ml-2")}>
{t("branches.overview.lastCommit")}{" "}
<DateFromNow className={classNames("is-size-7", "has-text-grey")} date={branch.lastCommitDate} />
</div>
</div>
<div className="media-right"> <div className="media-right">
<BranchButtonGroup repository={repository} branch={branch} /> <BranchButtonGroup repository={repository} branch={branch} />
</div> </div>

View File

@@ -23,11 +23,11 @@
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import { Link as ReactLink } from "react-router-dom"; import { Link as ReactLink } from "react-router-dom";
import { Branch, Link } from "@scm-manager/ui-types";
import DefaultBranchTag from "./DefaultBranchTag";
import { DateFromNow, Icon } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import styled from "styled-components"; import classNames from "classnames";
import { Branch, Link } from "@scm-manager/ui-types";
import { DateFromNow, Icon } from "@scm-manager/ui-components";
import DefaultBranchTag from "./DefaultBranchTag";
type Props = { type Props = {
baseUrl: string; baseUrl: string;
@@ -35,11 +35,6 @@ type Props = {
onDelete: (branch: Branch) => void; onDelete: (branch: Branch) => void;
}; };
const Created = styled.span`
margin-left: 1rem;
font-size: 0.8rem;
`;
const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => { const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => {
const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`; const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`;
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
@@ -63,9 +58,9 @@ const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => {
<DefaultBranchTag defaultBranch={branch.defaultBranch} /> <DefaultBranchTag defaultBranch={branch.defaultBranch} />
</ReactLink> </ReactLink>
{branch.lastCommitDate && ( {branch.lastCommitDate && (
<Created className="has-text-grey is-ellipsis-overflow"> <span className={classNames("has-text-grey", "is-ellipsis-overflow", "is-size-7", "ml-4")}>
{t("branches.table.lastCommit")} <DateFromNow date={branch.lastCommitDate} /> {t("branches.table.lastCommit")} <DateFromNow date={branch.lastCommitDate} />
</Created> </span>
)} )}
</td> </td>
<td className="is-darker">{deleteButton}</td> <td className="is-darker">{deleteButton}</td>

View File

@@ -23,24 +23,18 @@
*/ */
import React from "react"; import React from "react";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import styled from "styled-components";
import { Tag } from "@scm-manager/ui-components"; import { Tag } from "@scm-manager/ui-components";
type Props = WithTranslation & { type Props = WithTranslation & {
defaultBranch?: boolean; defaultBranch?: boolean;
}; };
const LeftMarginTag = styled(Tag)`
vertical-align: inherit;
margin-left: 0.75rem;
`;
class DefaultBranchTag extends React.Component<Props> { class DefaultBranchTag extends React.Component<Props> {
render() { render() {
const { defaultBranch, t } = this.props; const { defaultBranch, t } = this.props;
if (defaultBranch) { if (defaultBranch) {
return <LeftMarginTag color="dark" label={t("branch.defaultTag")} />; return <Tag className="ml-3" color="dark" label={t("branch.defaultTag")} />;
} }
return null; return null;
} }

View File

@@ -46,10 +46,6 @@ const LeftOverflowTd = styled.td`
text-align: left !important; text-align: left !important;
`; `;
const ResultNotification = styled(Notification)`
margin: 1rem;
`;
type PathResultRowProps = { type PathResultRowProps = {
contentBaseUrl: string; contentBaseUrl: string;
path: string; path: string;
@@ -81,7 +77,7 @@ type ResultTableProps = {
const ResultTable: FC<ResultTableProps> = ({ contentBaseUrl, paths }) => ( const ResultTable: FC<ResultTableProps> = ({ contentBaseUrl, paths }) => (
<table className="table table-hover table-sm is-fullwidth"> <table className="table table-hover table-sm is-fullwidth">
<tbody> <tbody>
{paths.map(path => ( {paths.map((path) => (
<PathResultRow contentBaseUrl={contentBaseUrl} path={path} /> <PathResultRow contentBaseUrl={contentBaseUrl} path={path} />
))} ))}
</tbody> </tbody>
@@ -92,13 +88,17 @@ const FileSearchResults: FC<Props> = ({ query, contentBaseUrl, paths = [] }) =>
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
let body; let body;
if (query.length <= 1) { if (query.length <= 1) {
body = <ResultNotification type="info">{t("filesearch.notifications.queryToShort")}</ResultNotification>; body = (
<Notification className="m-4" type="info">
{t("filesearch.notifications.queryToShort")}
</Notification>
);
} else if (paths.length === 0) { } else if (paths.length === 0) {
const queryCmp = <strong>{query}</strong>; const queryCmp = <strong>{query}</strong>;
body = ( body = (
<ResultNotification type="info"> <Notification className="m-4" type="info">
<Trans i18nKey="repos:filesearch.notifications.emptyResult" values={{ query }} components={[queryCmp]} /> <Trans i18nKey="repos:filesearch.notifications.emptyResult" values={{ query }} components={[queryCmp]} />
</ResultNotification> </Notification>
); );
} else { } else {
body = <ResultTable contentBaseUrl={contentBaseUrl} paths={paths} />; body = <ResultTable contentBaseUrl={contentBaseUrl} paths={paths} />;

View File

@@ -22,14 +22,15 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useEffect, useState } from "react"; import React, { FC, useEffect, useState } from "react";
import { Branch, Repository } from "@scm-manager/ui-types";
import { Link, useHistory, useLocation, useParams } from "react-router-dom"; import { Link, useHistory, useLocation, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import styled from "styled-components";
import { Branch, Repository } from "@scm-manager/ui-types";
import { urls, usePaths } from "@scm-manager/ui-api"; import { urls, usePaths } from "@scm-manager/ui-api";
import { ErrorNotification, FilterInput, Help, Icon, Loading } from "@scm-manager/ui-components"; import { ErrorNotification, FilterInput, Help, Icon, Loading } from "@scm-manager/ui-components";
import CodeActionBar from "../components/CodeActionBar"; import CodeActionBar from "../components/CodeActionBar";
import styled from "styled-components";
import FileSearchResults from "../components/FileSearchResults"; import FileSearchResults from "../components/FileSearchResults";
import { useTranslation } from "react-i18next";
import { filepathSearch } from "../utils/filepathSearch"; import { filepathSearch } from "../utils/filepathSearch";
type Props = { type Props = {
@@ -43,27 +44,14 @@ type Params = {
revision: string; revision: string;
}; };
const InputContainer = styled.div`
padding: 1rem 1.75rem 0 1.75rem;
display: flex;
align-items: center;
justify-content: flex-start;
`;
const HomeLink = styled(Link)` const HomeLink = styled(Link)`
border-right: 1px solid lightgray; border-right: 1px solid lightgray;
margin-right: 0.75rem;
padding-right: 0.75em;
`; `;
const HomeIcon = styled(Icon)` const HomeIcon = styled(Icon)`
line-height: 1.5rem; line-height: 1.5rem;
`; `;
const SearchHelp = styled(Help)`
margin-left: 0.75rem;
`;
const useRevision = () => { const useRevision = () => {
const { revision } = useParams<Params>(); const { revision } = useParams<Params>();
return revision; return revision;
@@ -113,8 +101,18 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch }
switchViewLink={evaluateSwitchViewLink} switchViewLink={evaluateSwitchViewLink}
/> />
<div className="panel"> <div className="panel">
<InputContainer> <div
<HomeLink to={contentBaseUrl}> className={classNames(
"is-flex",
"is-justify-content-flex-start",
"is-align-items-center",
"pt-4",
"mx-3",
"px-4",
"pb-0"
)}
>
<HomeLink className={classNames("mr-3", "pr-3")} to={contentBaseUrl}>
<HomeIcon title={t("filesearch.home")} name="home" color="inherit" /> <HomeIcon title={t("filesearch.home")} name="home" color="inherit" />
</HomeLink> </HomeLink>
<FilterInput <FilterInput
@@ -124,8 +122,8 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch }
filter={search} filter={search}
autoFocus={true} autoFocus={true}
/> />
<SearchHelp message={t("filesearch.input.help")} /> <Help className="ml-3" message={t("filesearch.input.help")} />
</InputContainer> </div>
<ErrorNotification error={error} /> <ErrorNotification error={error} />
{isLoading ? <Loading /> : <FileSearchResults contentBaseUrl={contentBaseUrl} query={query} paths={result} />} {isLoading ? <Loading /> : <FileSearchResults contentBaseUrl={contentBaseUrl} query={query} paths={result} />}
</div> </div>

View File

@@ -21,12 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import { RepositoryUrlImport } from "@scm-manager/ui-types"; import { RepositoryUrlImport } from "@scm-manager/ui-types";
import { InputField, validation } from "@scm-manager/ui-components"; import { InputField, validation } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
type Props = { type Props = {
repository: RepositoryUrlImport; repository: RepositoryUrlImport;
@@ -35,14 +33,6 @@ type Props = {
disabled?: boolean; disabled?: boolean;
}; };
const Column = styled.div`
padding: 0 0.75rem;
`;
const Columns = styled.div`
padding: 0.75rem 0 0;
`;
const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled }) => { const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
const [urlValidationError, setUrlValidationError] = useState(false); const [urlValidationError, setUrlValidationError] = useState(false);
@@ -65,8 +55,8 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled
}; };
return ( return (
<Columns className="columns is-multiline"> <div className="columns is-multiline pt-3">
<Column className="column is-full"> <div className="column is-full px-3">
<InputField <InputField
label={t("import.importUrl")} label={t("import.importUrl")}
onChange={handleImportUrlChange} onChange={handleImportUrlChange}
@@ -77,27 +67,27 @@ const ImportFromUrlForm: FC<Props> = ({ repository, onChange, setValid, disabled
disabled={disabled} disabled={disabled}
onBlur={handleImportUrlBlur} onBlur={handleImportUrlBlur}
/> />
</Column> </div>
<Column className="column is-half"> <div className="column is-half px-3">
<InputField <InputField
label={t("import.username")} label={t("import.username")}
onChange={username => onChange({ ...repository, username })} onChange={(username) => onChange({ ...repository, username })}
value={repository.username} value={repository.username}
helpText={t("help.usernameHelpText")} helpText={t("help.usernameHelpText")}
disabled={disabled} disabled={disabled}
/> />
</Column> </div>
<Column className="column is-half"> <div className="column is-half px-3">
<InputField <InputField
label={t("import.password")} label={t("import.password")}
onChange={password => onChange({ ...repository, password })} onChange={(password) => onChange({ ...repository, password })}
value={repository.password} value={repository.password}
type="password" type="password"
helpText={t("help.passwordHelpText")} helpText={t("help.passwordHelpText")}
disabled={disabled} disabled={disabled}
/> />
</Column> </div>
</Columns> </div>
); );
}; };

View File

@@ -42,7 +42,7 @@ import {
Icon, Icon,
Level, Level,
SignatureIcon, SignatureIcon,
Tooltip Tooltip,
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import ContributorTable from "./ContributorTable"; import ContributorTable from "./ContributorTable";
import { Link as ReactLink } from "react-router-dom"; import { Link as ReactLink } from "react-router-dom";
@@ -54,14 +54,6 @@ type Props = {
fileControlFactory?: FileControlFactory; fileControlFactory?: FileControlFactory;
}; };
const RightMarginP = styled.p`
margin-right: 1em;
`;
const BottomMarginLevel = styled(Level)`
margin-bottom: 1rem !important;
`;
const countContributors = (changeset: Changeset) => { const countContributors = (changeset: Changeset) => {
if (changeset.contributors) { if (changeset.contributors) {
return changeset.contributors.length + 1; return changeset.contributors.length + 1;
@@ -69,16 +61,6 @@ const countContributors = (changeset: Changeset) => {
return 1; return 1;
}; };
const FlexRow = styled.div`
display: flex;
flex-direction: row;
`;
const ContributorLine = styled.div`
display: flex;
cursor: pointer;
`;
const ContributorColumn = styled.p` const ContributorColumn = styled.p`
flex-grow: 0; flex-grow: 0;
overflow: hidden; overflow: hidden;
@@ -92,25 +74,7 @@ const CountColumn = styled.p`
white-space: nowrap; white-space: nowrap;
`; `;
const ContributorDetails = styled.div`
display: flex;
flex-direction: column;
margin-bottom: 1rem;
`;
const ContributorToggleLine = styled.p`
cursor: pointer;
/** margin-bottom is inherit from content p **/
margin-bottom: 0.5rem !important;
`;
const ChangesetSummary = styled.div`
display: flex;
`;
const SeparatedParents = styled.div` const SeparatedParents = styled.div`
margin-left: 1em;
a + a:before { a + a:before {
content: ",\\00A0"; content: ",\\00A0";
color: #4a4a4a; color: #4a4a4a;
@@ -126,33 +90,33 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => {
if (open) { if (open) {
return ( return (
<ContributorDetails> <div className="is-flex is-flex-direction-column mb-4">
<FlexRow> <div className="is-flex">
<ContributorToggleLine onClick={e => setOpen(!open)} className="is-ellipsis-overflow"> <p className="is-ellipsis-overflow is-clickable mb-2" onClick={(e) => setOpen(!open)}>
<Icon name="angle-down" /> {t("changeset.contributors.list")} <Icon name="angle-down" /> {t("changeset.contributors.list")}
</ContributorToggleLine> </p>
{signatureIcon} {signatureIcon}
</FlexRow> </div>
<ContributorTable changeset={changeset} /> <ContributorTable changeset={changeset} />
</ContributorDetails> </div>
); );
} }
return ( return (
<> <>
<ContributorLine onClick={e => setOpen(!open)}> <div className="is-flex is-clickable" onClick={(e) => setOpen(!open)}>
<ContributorColumn className="is-ellipsis-overflow"> <ContributorColumn className="is-ellipsis-overflow">
<Icon name="angle-right" /> <ChangesetAuthor changeset={changeset} /> <Icon name="angle-right" /> <ChangesetAuthor changeset={changeset} />
</ContributorColumn> </ContributorColumn>
{signatureIcon} {signatureIcon}
<CountColumn className={"is-hidden-mobile is-hidden-tablet-only is-hidden-desktop-only"}> <CountColumn className="is-hidden-mobile is-hidden-tablet-only is-hidden-desktop-only">
( (
<span className="has-text-link"> <span className="has-text-link">
{t("changeset.contributors.count", { count: countContributors(changeset) })} {t("changeset.contributors.count", { count: countContributors(changeset) })}
</span> </span>
) )
</CountColumn> </CountColumn>
</ContributorLine> </div>
</> </>
); );
}; };
@@ -178,13 +142,13 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
return ( return (
<> <>
<div className={classNames("content", "is-marginless")}> <div className={classNames("content", "m-0")}>
<h4> <h4>
<ExtensionPoint <ExtensionPoint
name="changeset.description" name="changeset.description"
props={{ props={{
changeset, changeset,
value: description.title value: description.title,
}} }}
renderAll={false} renderAll={false}
> >
@@ -193,23 +157,23 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
</h4> </h4>
<article className="media"> <article className="media">
<AvatarWrapper> <AvatarWrapper>
<RightMarginP className={classNames("image", "is-64x64")}> <p className={classNames("image", "is-64x64", "mr-4")}>
<AvatarImage person={changeset.author} /> <AvatarImage person={changeset.author} />
</RightMarginP> </p>
</AvatarWrapper> </AvatarWrapper>
<div className="media-content"> <div className="media-content">
<Contributors changeset={changeset} /> <Contributors changeset={changeset} />
<ChangesetSummary className="is-ellipsis-overflow"> <div className="is-flex is-ellipsis-overflow">
<p> <p>
<Trans i18nKey="repos:changeset.summary" components={[id, date]} /> <Trans i18nKey="repos:changeset.summary" components={[id, date]} />
</p> </p>
{parents && parents?.length > 0 ? ( {parents && parents?.length > 0 ? (
<SeparatedParents> <SeparatedParents className="ml-4">
{t("changeset.parents.label", { count: parents?.length }) + ": "} {t("changeset.parents.label", { count: parents?.length }) + ": "}
{parents} {parents}
</SeparatedParents> </SeparatedParents>
) : null} ) : null}
</ChangesetSummary> </div>
</div> </div>
<div className="media-right"> <div className="media-right">
<ChangesetTags changeset={changeset} /> <ChangesetTags changeset={changeset} />
@@ -244,7 +208,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
name="changeset.description" name="changeset.description"
props={{ props={{
changeset, changeset,
value: item value: item,
}} }}
renderAll={false} renderAll={false}
> >
@@ -257,7 +221,8 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
</p> </p>
</div> </div>
<div> <div>
<BottomMarginLevel <Level
className="mb-4"
right={ right={
<Button <Button
action={collapseDiffs} action={collapseDiffs}

View File

@@ -97,7 +97,7 @@ const ContributorTable: FC<Props> = ({ changeset }) => {
{getContributorsByType().map(contributor => ( {getContributorsByType().map(contributor => (
<tr key={contributor.type}> <tr key={contributor.type}>
<SizedTd>{t("changeset.contributor.type." + contributor.type)}:</SizedTd> <SizedTd>{t("changeset.contributor.type." + contributor.type)}:</SizedTd>
<td className="is-ellipsis-overflow is-marginless"> <td className="is-ellipsis-overflow m-0">
<CommaSeparatedList> <CommaSeparatedList>
{contributor.persons!.map(person => ( {contributor.persons!.map(person => (
<Contributor key={person.name} person={person} /> <Contributor key={person.name} person={person} />

View File

@@ -22,28 +22,14 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useCallback, useEffect, useState } from "react"; import React, { FC, useCallback, useEffect, useState } from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { IndexResources, Repository, RepositoryType, CUSTOM_NAMESPACE_STRATEGY } from "@scm-manager/ui-types"; import { IndexResources, Repository, RepositoryType, CUSTOM_NAMESPACE_STRATEGY } from "@scm-manager/ui-types";
import { Checkbox, Level, Select, SubmitButton } from "@scm-manager/ui-components"; import { Checkbox, Level, Select, SubmitButton } from "@scm-manager/ui-components";
import NamespaceAndNameFields from "../NamespaceAndNameFields"; import NamespaceAndNameFields from "../NamespaceAndNameFields";
import RepositoryInformationForm from "../RepositoryInformationForm"; import RepositoryInformationForm from "../RepositoryInformationForm";
const CheckboxWrapper = styled.div`
margin-top: 2em;
flex: 1;
`;
const SelectWrapper = styled.div`
flex: 1;
`;
const SpaceBetween = styled.div`
display: flex;
justify-content: space-between;
`;
type Props = { type Props = {
createRepository?: (repo: RepositoryCreation, shouldInit: boolean) => void; createRepository?: (repo: RepositoryCreation, shouldInit: boolean) => void;
modifyRepository?: (repo: Repository) => void; modifyRepository?: (repo: Repository) => void;
@@ -65,7 +51,7 @@ const RepositoryForm: FC<Props> = ({
repositoryTypes, repositoryTypes,
namespaceStrategy, namespaceStrategy,
loading, loading,
indexResources indexResources,
}) => { }) => {
const [repo, setRepo] = useState<Repository>({ const [repo, setRepo] = useState<Repository>({
name: "", name: "",
@@ -73,15 +59,15 @@ const RepositoryForm: FC<Props> = ({
type: "", type: "",
contact: "", contact: "",
description: "", description: "",
_links: {} _links: {},
}); });
const [initRepository, setInitRepository] = useState(false); const [initRepository, setInitRepository] = useState(false);
const [contextEntries, setContextEntries] = useState({}); const [contextEntries, setContextEntries] = useState({});
const setCreationContextEntry = useCallback( const setCreationContextEntry = useCallback(
(key: string, value: any) => { (key: string, value: any) => {
setContextEntries(entries => ({ setContextEntries((entries) => ({
...entries, ...entries,
[key]: value [key]: value,
})); }));
}, },
[setContextEntries] [setContextEntries]
@@ -102,7 +88,7 @@ const RepositoryForm: FC<Props> = ({
const isValid = () => { const isValid = () => {
return ( return (
!(!repo.name || (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !repo.namespace)) && !(!repo.name || (namespaceStrategy === CUSTOM_NAMESPACE_STRATEGY && !repo.namespace)) &&
Object.values(valid).every(v => v) Object.values(valid).every((v) => v)
); );
}; };
@@ -119,10 +105,10 @@ const RepositoryForm: FC<Props> = ({
const createSelectOptions = (repositoryTypes?: RepositoryType[]) => { const createSelectOptions = (repositoryTypes?: RepositoryType[]) => {
if (repositoryTypes) { if (repositoryTypes) {
return repositoryTypes.map(repositoryType => { return repositoryTypes.map((repositoryType) => {
return { return {
label: repositoryType.displayName, label: repositoryType.displayName,
value: repositoryType.name value: repositoryType.name,
}; };
}); });
} }
@@ -137,28 +123,28 @@ const RepositoryForm: FC<Props> = ({
const extensionProps = { const extensionProps = {
repository: repo, repository: repo,
setCreationContextEntry: setCreationContextEntry, setCreationContextEntry: setCreationContextEntry,
indexResources: indexResourcesWithLinks indexResources: indexResourcesWithLinks,
}; };
return ( return (
<> <>
<NamespaceAndNameFields <NamespaceAndNameFields
repository={repo} repository={repo}
onChange={setRepo} onChange={setRepo}
setValid={namespaceAndName => setValid({ ...valid, namespaceAndName })} setValid={(namespaceAndName) => setValid({ ...valid, namespaceAndName })}
disabled={disabled} disabled={disabled}
/> />
<SpaceBetween> <div className="columns">
<SelectWrapper> <div className={classNames("column", "is-half")}>
<Select <Select
label={t("repository.type")} label={t("repository.type")}
onChange={type => setRepo({ ...repo, type })} onChange={(type) => setRepo({ ...repo, type })}
value={repo ? repo.type : ""} value={repo ? repo.type : ""}
options={createSelectOptions(repositoryTypes)} options={createSelectOptions(repositoryTypes)}
helpText={t("help.typeHelpText")} helpText={t("help.typeHelpText")}
disabled={disabled} disabled={disabled}
/> />
</SelectWrapper> </div>
<CheckboxWrapper> <div className={classNames("column", "is-half", "is-align-self-flex-end")}>
<Checkbox <Checkbox
label={t("repositoryForm.initializeRepository")} label={t("repositoryForm.initializeRepository")}
checked={initRepository} checked={initRepository}
@@ -169,8 +155,8 @@ const RepositoryForm: FC<Props> = ({
{initRepository && ( {initRepository && (
<ExtensionPoint name="repos.create.initialize" props={extensionProps} renderAll={true} /> <ExtensionPoint name="repos.create.initialize" props={extensionProps} renderAll={true} />
)} )}
</CheckboxWrapper> </div>
</SpaceBetween> </div>
</> </>
); );
}; };
@@ -199,7 +185,7 @@ const RepositoryForm: FC<Props> = ({
repository={repo} repository={repo}
onChange={setRepo} onChange={setRepo}
disabled={disabled} disabled={disabled}
setValid={contact => setValid({ ...valid, contact })} setValid={(contact) => setValid({ ...valid, contact })}
/> />
{submitButton()} {submitButton()}
</form> </form>

View File

@@ -21,22 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import styled from "styled-components";
import { Button, ButtonAddons, Icon, Level, urls } from "@scm-manager/ui-components";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames";
const MarginIcon = styled(Icon)` import styled from "styled-components";
padding-right: 0.5rem; import { Button, ButtonAddons, Icon, Level, urls } from "@scm-manager/ui-components";
`;
const SmallButton = styled(Button)`
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
`;
const TopLevel = styled(Level)` const TopLevel = styled(Level)`
margin-top: 1.5rem; margin-top: 1.5rem;
@@ -62,10 +52,14 @@ const RepositoryFormButton: FC<RepositoryForm> = ({ path, icon, label }) => {
const [t] = useTranslation(["repos", "plugins"]); const [t] = useTranslation(["repos", "plugins"]);
return ( return (
<SmallButton color={isSelected ? "link is-selected" : undefined} link={!isSelected ? href : undefined}> <Button
<MarginIcon name={icon} color={isSelected ? "white" : "default"} /> className="is-size-6"
<p className="is-hidden-mobile is-hidden-tablet-only">{t(`plugins:${label}`, label)}</p> color={isSelected ? "link is-selected" : undefined}
</SmallButton> link={!isSelected ? href : undefined}
>
<Icon className="pr-2" name={icon} color={isSelected ? "white" : "default"} />
<p className={classNames("is-hidden-mobile", "is-hidden-tablet-only")}>{t(`plugins:${label}`, label)}</p>
</Button>
); );
}; };

View File

@@ -37,12 +37,12 @@ const RepositoryGroupEntry: FC<Props> = ({ group }) => {
const settingsLink = group.namespace?._links?.permissions && ( const settingsLink = group.namespace?._links?.permissions && (
<Link to={`/namespace/${group.name}/settings`}> <Link to={`/namespace/${group.name}/settings`}>
<Icon color="is-link" name="cog" title={t("repositoryOverview.settings.tooltip")} className={"is-size-6 ml-2"} /> <Icon color="is-link" name="cog" title={t("repositoryOverview.settings.tooltip")} className="is-size-6 ml-2" />
</Link> </Link>
); );
const namespaceHeader = ( const namespaceHeader = (
<> <>
<Link to={`/repos/${group.name}/`} className={"has-text-dark"}> <Link to={`/repos/${group.name}/`} className="has-text-dark">
{group.name} {group.name}
</Link>{" "} </Link>{" "}
{settingsLink} {settingsLink}

View File

@@ -31,18 +31,17 @@ import {
InputField, InputField,
Level, Level,
Notification, Notification,
Subtitle Subtitle,
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ExportInfo, Link, Repository } from "@scm-manager/ui-types"; import { ExportInfo, Link, Repository } from "@scm-manager/ui-types";
import { useExportInfo, useExportRepository } from "@scm-manager/ui-api"; import { useExportInfo, useExportRepository } from "@scm-manager/ui-api";
import styled from "styled-components"; import styled from "styled-components";
import classNames from "classnames";
const InfoBox = styled.div` const InfoBox = styled.div`
white-space: pre-line; white-space: pre-line;
background-color: #ccecf9; background-color: #ccecf9;
margin: 1rem 0;
padding: 1rem;
border-radius: 2px; border-radius: 2px;
border-left: 0.2rem solid; border-left: 0.2rem solid;
border-color: #33b2e8; border-color: #33b2e8;
@@ -60,7 +59,7 @@ const ExportInterruptedNotification = () => {
const ExportInfoBox: FC<{ exportInfo: ExportInfo }> = ({ exportInfo }) => { const ExportInfoBox: FC<{ exportInfo: ExportInfo }> = ({ exportInfo }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
return ( return (
<InfoBox> <InfoBox className={classNames("my-4", "p-4")}>
<strong>{t("export.exportInfo.infoBoxTitle")}</strong> <strong>{t("export.exportInfo.infoBoxTitle")}</strong>
<p>{t("export.exportInfo.exporter", { username: exportInfo.exporterName })}</p> <p>{t("export.exportInfo.exporter", { username: exportInfo.exporterName })}</p>
<p> <p>
@@ -90,7 +89,7 @@ const ExportRepository: FC<Props> = ({ repository }) => {
isLoading: isLoadingExport, isLoading: isLoadingExport,
error: errorExport, error: errorExport,
data: exportedInfo, data: exportedInfo,
exportRepository exportRepository,
} = useExportRepository(); } = useExportRepository();
useEffect(() => { useEffect(() => {
@@ -144,7 +143,7 @@ const ExportRepository: FC<Props> = ({ repository }) => {
helpText={t("export.encrypt.helpText")} helpText={t("export.encrypt.helpText")}
/> />
{encrypt && ( {encrypt && (
<div className="columns column is-half"> <div className={classNames("columns", "column", "is-half")}>
<InputField <InputField
label={t("export.password.label")} label={t("export.password.label")}
helpText={t("export.password.helpText")} helpText={t("export.password.helpText")}
@@ -172,7 +171,7 @@ const ExportRepository: FC<Props> = ({ repository }) => {
exportRepository(repository, { exportRepository(repository, {
compressed, compressed,
password: encrypt ? password : "", password: encrypt ? password : "",
withMetadata: fullExport withMetadata: fullExport,
}) })
} }
loading={isLoadingInfo || isLoadingExport} loading={isLoadingInfo || isLoadingExport}

View File

@@ -51,9 +51,9 @@ const HealthCheckWarning: FC<Props> = ({ repository }) => {
return ( return (
<Notification type="danger"> <Notification type="danger">
{modal} {modal}
<div className={"has-cursor-pointer"} onClick={() => setShowHealthCheck(true)}> <div className="is-clickable" onClick={() => setShowHealthCheck(true)}>
<div>{t("repositoryForm.healthCheckWarning.title")}</div> <div>{t("repositoryForm.healthCheckWarning.title")}</div>
<div className={"is-small"}>{t("repositoryForm.healthCheckWarning.subtitle")}</div> <div className="is-small">{t("repositoryForm.healthCheckWarning.subtitle")}</div>
</div> </div>
</Notification> </Notification>
); );

View File

@@ -21,14 +21,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { Repository } from "@scm-manager/ui-types"; import { Repository } from "@scm-manager/ui-types";
import { Subtitle } from "@scm-manager/ui-components";
import RenameRepository from "./RenameRepository"; import RenameRepository from "./RenameRepository";
import DeleteRepo from "./DeleteRepo"; import DeleteRepo from "./DeleteRepo";
import styled from "styled-components";
import { Subtitle } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import ArchiveRepo from "./ArchiveRepo"; import ArchiveRepo from "./ArchiveRepo";
import UnarchiveRepo from "./UnarchiveRepo"; import UnarchiveRepo from "./UnarchiveRepo";
@@ -37,7 +36,6 @@ type Props = {
}; };
export const DangerZoneContainer = styled.div` export const DangerZoneContainer = styled.div`
padding: 1.5rem 1rem;
border: 1px solid #ff6a88; border: 1px solid #ff6a88;
border-radius: 5px; border-radius: 5px;
@@ -83,7 +81,7 @@ const RepositoryDangerZone: FC<Props> = ({ repository }) => {
<> <>
<hr /> <hr />
<Subtitle subtitle={t("repositoryForm.dangerZone")} /> <Subtitle subtitle={t("repositoryForm.dangerZone")} />
<DangerZoneContainer>{dangerZone}</DangerZoneContainer> <DangerZoneContainer className="px-4 py-5">{dangerZone}</DangerZoneContainer>
</> </>
); );
}; };

View File

@@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { useState } from "react"; import React, { useState } from "react";
import { Link as RouteLink, match, Redirect, Route, Switch, useRouteMatch } from "react-router-dom"; import { Link as RouteLink, Redirect, Route, Switch, useRouteMatch } from "react-router-dom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions"; import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import { Changeset, Link } from "@scm-manager/ui-types"; import { Changeset, Link } from "@scm-manager/ui-types";
@@ -61,7 +61,6 @@ import { useIndexLinks, useRepository } from "@scm-manager/ui-api";
import styled from "styled-components"; import styled from "styled-components";
const TagGroup = styled.span` const TagGroup = styled.span`
font-weight: bold;
& > * { & > * {
margin-right: 0.25rem; margin-right: 0.25rem;
} }
@@ -159,7 +158,7 @@ const RepositoryRoot = () => {
const titleComponent = ( const titleComponent = (
<> <>
<RouteLink to={`/repos/${repository.namespace}/`} className={"has-text-dark"}> <RouteLink to={`/repos/${repository.namespace}/`} className="has-text-dark">
{repository.namespace} {repository.namespace}
</RouteLink> </RouteLink>
/{repository.name} /{repository.name}
@@ -220,7 +219,7 @@ const RepositoryRoot = () => {
afterTitle={ afterTitle={
<div className="is-flex"> <div className="is-flex">
<ExtensionPoint name={"repository.afterTitle"} props={{ repository }} /> <ExtensionPoint name={"repository.afterTitle"} props={{ repository }} />
<TagGroup> <TagGroup className="has-text-weight-bold">
<RepositoryFlags repository={repository} tooltipLocation="bottom" /> <RepositoryFlags repository={repository} tooltipLocation="bottom" />
</TagGroup> </TagGroup>
</div> </div>

View File

@@ -26,16 +26,11 @@ import { useTranslation } from "react-i18next";
import { Repository } from "@scm-manager/ui-types"; import { Repository } from "@scm-manager/ui-types";
import { Button, ErrorNotification, Level, Subtitle } from "@scm-manager/ui-components"; import { Button, ErrorNotification, Level, Subtitle } from "@scm-manager/ui-components";
import { useRunHealthCheck } from "@scm-manager/ui-api"; import { useRunHealthCheck } from "@scm-manager/ui-api";
import styled from "styled-components";
type Props = { type Props = {
repository: Repository; repository: Repository;
}; };
const MarginTopButton = styled(Button)`
margin-top: 1rem;
`;
const RunHealthCheck: FC<Props> = ({ repository }) => { const RunHealthCheck: FC<Props> = ({ repository }) => {
const { isLoading, error, runHealthCheck } = useRunHealthCheck(); const { isLoading, error, runHealthCheck } = useRunHealthCheck();
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
@@ -56,7 +51,8 @@ const RunHealthCheck: FC<Props> = ({ repository }) => {
</p> </p>
<Level <Level
right={ right={
<MarginTopButton <Button
className="mt-4"
color="warning" color="warning"
icon="heartbeat" icon="heartbeat"
label={t("runHealthCheck.button")} label={t("runHealthCheck.button")}

View File

@@ -24,7 +24,6 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { File } from "@scm-manager/ui-types"; import { File } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, MarkdownView } from "@scm-manager/ui-components"; import { ErrorNotification, Loading, MarkdownView } from "@scm-manager/ui-components";
import styled from "styled-components";
import replaceBranchWithRevision from "../../ReplaceBranchWithRevision"; import replaceBranchWithRevision from "../../ReplaceBranchWithRevision";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { useFileContent } from "@scm-manager/ui-api"; import { useFileContent } from "@scm-manager/ui-api";
@@ -34,10 +33,6 @@ type Props = {
basePath: string; basePath: string;
}; };
const MarkdownContent = styled.div`
padding: 0.5rem;
`;
const MarkdownViewer: FC<Props> = ({ file, basePath }) => { const MarkdownViewer: FC<Props> = ({ file, basePath }) => {
const { isLoading, error, data: content } = useFileContent(file); const { isLoading, error, data: content } = useFileContent(file);
const location = useLocation(); const location = useLocation();
@@ -53,9 +48,9 @@ const MarkdownViewer: FC<Props> = ({ file, basePath }) => {
const permalink = replaceBranchWithRevision(location.pathname, file.revision); const permalink = replaceBranchWithRevision(location.pathname, file.revision);
return ( return (
<MarkdownContent> <div className="p-2">
<MarkdownView content={content} basePath={basePath} permalink={permalink} enableAnchorHeadings={true} /> <MarkdownView content={content} basePath={basePath} permalink={permalink} enableAnchorHeadings={true} />
</MarkdownContent> </div>
); );
}; };

View File

@@ -21,13 +21,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, {FC, useState} from "react"; import React, { FC, useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import MarkdownViewer from "./MarkdownViewer"; import MarkdownViewer from "./MarkdownViewer";
import SourcecodeViewer from "./SourcecodeViewer"; import SourcecodeViewer from "./SourcecodeViewer";
import {File} from "@scm-manager/ui-types"; import { File } from "@scm-manager/ui-types";
import {Button} from "@scm-manager/ui-components"; import { Button } from "@scm-manager/ui-components";
import {useTranslation} from "react-i18next"; import { useTranslation } from "react-i18next";
const ToggleButton = styled(Button)` const ToggleButton = styled(Button)`
max-width: 1rem; max-width: 1rem;
@@ -37,17 +37,13 @@ const ToggleButton = styled(Button)`
z-index: 30; z-index: 30;
`; `;
const Container = styled.div`
position: relative;
`;
type Props = { type Props = {
file: File; file: File;
basePath: string; basePath: string;
}; };
const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => { const SwitchableMarkdownViewer: FC<Props> = ({ file, basePath }) => {
const {t} = useTranslation("repos"); const { t } = useTranslation("repos");
const [renderMarkdown, setRenderMarkdown] = useState(true); const [renderMarkdown, setRenderMarkdown] = useState(true);
const toggleMarkdown = () => { const toggleMarkdown = () => {
@@ -55,7 +51,7 @@ const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => {
}; };
return ( return (
<Container> <div className="is-relative">
<ToggleButton <ToggleButton
color={renderMarkdown ? "link" : ""} color={renderMarkdown ? "link" : ""}
action={toggleMarkdown} action={toggleMarkdown}
@@ -65,11 +61,14 @@ const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => {
: t("sources.content.toggleButton.showMarkdown") : t("sources.content.toggleButton.showMarkdown")
} }
> >
<i className="fab fa-markdown"/> <i className="fab fa-markdown" />
</ToggleButton> </ToggleButton>
{renderMarkdown ? <MarkdownViewer file={file} basePath={basePath}/> : {renderMarkdown ? (
<SourcecodeViewer file={file} language={"MARKDOWN"}/>} <MarkdownViewer file={file} basePath={basePath} />
</Container> ) : (
<SourcecodeViewer file={file} language={"MARKDOWN"} />
)}
</div>
); );
}; };

View File

@@ -49,14 +49,6 @@ const HeaderWrapper = styled.div`
padding: 0.5em 0.75em; padding: 0.5em 0.75em;
`; `;
const LighterGreyBackgroundPanelBlock = styled.div`
background-color: #fbfbfb;
`;
const LighterGreyBackgroundTable = styled.table`
background-color: #fbfbfb;
`;
const BorderBottom = styled.div` const BorderBottom = styled.div`
border-bottom: solid 1px #dbdbdb; border-bottom: solid 1px #dbdbdb;
`; `;
@@ -65,10 +57,6 @@ const FullWidthTitleHeader = styled.div`
max-width: 100%; max-width: 100%;
`; `;
const AlignRight = styled.div`
margin-left: auto;
`;
const BorderLessDiv = styled.div` const BorderLessDiv = styled.div`
margin: -1.25rem; margin: -1.25rem;
border: none; border: none;
@@ -116,13 +104,13 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
<HeaderWrapper> <HeaderWrapper>
<div className={classNames("level", "is-flex-wrap-wrap")}> <div className={classNames("level", "is-flex-wrap-wrap")}>
<FullWidthTitleHeader <FullWidthTitleHeader
className={classNames("level-left", "is-flex", "has-cursor-pointer", "is-word-break", "mr-2")} className={classNames("level-left", "is-flex", "is-clickable", "is-word-break", "mr-2")}
onClick={toggleCollapse} onClick={toggleCollapse}
> >
<Icon className={classNames("is-inline", "mr-2")} name={`${icon} fa-fw`} color="inherit" /> <Icon className={classNames("is-inline", "mr-2")} name={`${icon} fa-fw`} color="inherit" />
{file.name} {file.name}
</FullWidthTitleHeader> </FullWidthTitleHeader>
<AlignRight className={classNames("level-right", "buttons")}> <div className={classNames("level-right", "buttons", "ml-auto")}>
{selector} {selector}
<OpenInFullscreenButton <OpenInFullscreenButton
modalTitle={file?.name} modalTitle={file?.name}
@@ -139,7 +127,7 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
}} }}
renderAll={true} renderAll={true}
/> />
</AlignRight> </div>
</div> </div>
</HeaderWrapper> </HeaderWrapper>
); );
@@ -163,8 +151,8 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
if (!collapsed) { if (!collapsed) {
return ( return (
<> <>
<LighterGreyBackgroundPanelBlock className="panel-block"> <div className="panel-block has-background-white-bis">
<LighterGreyBackgroundTable className="table"> <table className="table has-background-white-bis">
<tbody> <tbody>
<tr> <tr>
<td>{t("sources.content.path")}</td> <td>{t("sources.content.path")}</td>
@@ -198,8 +186,8 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
}} }}
/> />
</tbody> </tbody>
</LighterGreyBackgroundTable> </table>
</LighterGreyBackgroundPanelBlock> </div>
<BorderBottom /> <BorderBottom />
</> </>
); );

View File

@@ -21,12 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { Repository, Tag } from "@scm-manager/ui-types"; import { Repository, Tag } from "@scm-manager/ui-types";
import { DateFromNow, SignatureIcon } from "@scm-manager/ui-components"; import { DateFromNow, SignatureIcon } from "@scm-manager/ui-components";
import styled from "styled-components";
import TagButtonGroup from "./TagButtonGroup"; import TagButtonGroup from "./TagButtonGroup";
type Props = { type Props = {
@@ -34,37 +33,22 @@ type Props = {
tag: Tag; tag: Tag;
}; };
const FlexRow = styled.div`
display: flex;
align-items: center;
flex-wrap: wrap;
`;
const Created = styled.div`
margin-left: 0.5rem;
font-size: 0.8rem;
`;
const Label = styled.strong`
margin-right: 0.3rem;
`;
const Date = styled(DateFromNow)`
font-size: 0.8rem;
`;
const TagDetail: FC<Props> = ({ tag, repository }) => { const TagDetail: FC<Props> = ({ tag, repository }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
return ( return (
<div className="media"> <div className="media">
<FlexRow className="media-content"> <div className={classNames("media-content", "is-flex", "is-flex-wrap-wrap", "is-align-items-center")}>
<Label className="subtitle has-text-weight-bold has-text-black">{t("tag.name") + ": "} </Label> <span className="subtitle">{tag.name}</span> <strong className={classNames("subtitle", "has-text-weight-bold", "has-text-black", "mr-1")}>
{t("tag.name") + ": "}{" "}
</strong>{" "}
<span className="subtitle">{tag.name}</span>
<SignatureIcon signatures={tag.signatures} className="ml-2 mb-5" /> <SignatureIcon signatures={tag.signatures} className="ml-2 mb-5" />
<Created className="is-ellipsis-overflow mb-5"> <div className={classNames("is-ellipsis-overflow", "mb-5", "ml-2", "is-size-7")}>
{t("tags.overview.created")} <Date date={tag.date} className="has-text-grey" /> {t("tags.overview.created")}{" "}
</Created> <DateFromNow className={classNames("has-text-grey", "is-size-7")} date={tag.date} />
</FlexRow> </div>
</div>
<div className="media-right"> <div className="media-right">
<TagButtonGroup repository={repository} tag={tag} /> <TagButtonGroup repository={repository} tag={tag} />
</div> </div>

View File

@@ -21,12 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC } from "react"; import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom"; import { Link as RouterLink } from "react-router-dom";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { Tag, Link } from "@scm-manager/ui-types"; import { Tag, Link } from "@scm-manager/ui-types";
import styled from "styled-components";
import { DateFromNow, Icon } from "@scm-manager/ui-components"; import { DateFromNow, Icon } from "@scm-manager/ui-components";
type Props = { type Props = {
@@ -36,11 +35,6 @@ type Props = {
// deleting: boolean; // deleting: boolean;
}; };
const Created = styled.span`
margin-left: 1rem;
font-size: 0.8rem;
`;
const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => { const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
@@ -61,9 +55,9 @@ const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => {
<td> <td>
<RouterLink to={to} title={tag.name}> <RouterLink to={to} title={tag.name}>
{tag.name} {tag.name}
<Created className="has-text-grey is-ellipsis-overflow"> <span className={classNames("has-text-grey", "is-ellipsis-overflow", "ml-2", "is-size-7")}>
{t("tags.overview.created")} <DateFromNow date={tag.date} /> {t("tags.overview.created")} <DateFromNow date={tag.date} />
</Created> </span>
</RouterLink> </RouterLink>
</td> </td>
<td className="is-darker">{deleteButton}</td> <td className="is-darker">{deleteButton}</td>

View File

@@ -53,7 +53,7 @@ type CountProps = {
const Count: FC<CountProps> = ({ isLoading, isSelected, count }) => { const Count: FC<CountProps> = ({ isLoading, isSelected, count }) => {
if (isLoading) { if (isLoading) {
return <span className={"small-loading-spinner"} />; return <span className="small-loading-spinner" />;
} }
return ( return (
<Tag rounded={true} color={isSelected ? "info" : "light"}> <Tag rounded={true} color={isSelected ? "info" : "light"}>

View File

@@ -22,24 +22,18 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { FC, useEffect, useState } from "react"; import React, { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { User } from "@scm-manager/ui-types";
import { useConvertToExternal, useConvertToInternal } from "@scm-manager/ui-api";
import { import {
Button, Button,
ErrorNotification, ErrorNotification,
Level, Level,
Modal, Modal,
PasswordConfirmation, PasswordConfirmation,
SubmitButton SubmitButton,
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { User } from "@scm-manager/ui-types";
import styled from "styled-components";
import { useConvertToExternal, useConvertToInternal } from "@scm-manager/ui-api";
const ExternalDescription = styled.div`
display: flex;
align-items: center;
font-weight: 400;
`;
type Props = { type Props = {
user: User; user: User;
@@ -53,12 +47,12 @@ const UserConverter: FC<Props> = ({ user }) => {
const { const {
isLoading: isConvertingToInternal, isLoading: isConvertingToInternal,
error: convertingToInternalError, error: convertingToInternalError,
convertToInternal convertToInternal,
} = useConvertToInternal(); } = useConvertToInternal();
const { const {
isLoading: isConvertingToExternal, isLoading: isConvertingToExternal,
error: convertingToExternalError, error: convertingToExternalError,
convertToExternal convertToExternal,
} = useConvertToExternal(); } = useConvertToExternal();
const error = convertingToExternalError || convertingToInternalError || undefined; const error = convertingToExternalError || convertingToInternalError || undefined;
const isLoading = isConvertingToExternal || isConvertingToInternal; const isLoading = isConvertingToExternal || isConvertingToInternal;
@@ -135,7 +129,9 @@ const UserConverter: FC<Props> = ({ user }) => {
{showPasswordModal && passwordModal} {showPasswordModal && passwordModal}
{error && <ErrorNotification error={error} />} {error && <ErrorNotification error={error} />}
<div className="columns is-multiline"> <div className="columns is-multiline">
<ExternalDescription className="column is-half">{getUserExternalDescription()}</ExternalDescription> <div className={classNames("column", "is-half", "is-flex", "is-align-items-center")}>
{getUserExternalDescription()}
</div>
<div className="column is-half"> <div className="column is-half">
<Level right={getConvertButton()} /> <Level right={getConvertButton()} />
</div> </div>

View File

@@ -56,19 +56,19 @@ const ApiKeyCreatedModal: FC<Props> = ({ addedKey, close }) => {
}; };
const newPassphraseModalContent = ( const newPassphraseModalContent = (
<div className={"media-content"}> <div className="media-content">
<p>{t("apiKey.modal.text1")}</p> <p>{t("apiKey.modal.text1")}</p>
<p> <p>
<b>{t("apiKey.modal.text2")}</b> <b>{t("apiKey.modal.text2")}</b>
</p> </p>
<hr /> <hr />
<div className={"columns"}> <div className="columns">
<div className={"column is-11"}> <div className="column is-11">
<KeyArea wrap={"soft"} ref={keyRef} className={"input"} value={addedKey.token} /> <KeyArea wrap={"soft"} ref={keyRef} className="input" value={addedKey.token} />
</div> </div>
<NoLeftMargin className={"column is-1"}> <NoLeftMargin className="column is-1">
<Icon <Icon
className={"is-hidden-mobile fa-2x"} className="is-hidden-mobile fa-2x"
name={copied ? "clipboard-check" : "clipboard"} name={copied ? "clipboard-check" : "clipboard"}
title={t("apiKey.modal.clipboard")} title={t("apiKey.modal.clipboard")}
onClick={copy} onClick={copy}