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

View File

@@ -44,10 +44,6 @@ const MinWidthControl = styled.div`
min-width: 10rem;
`;
const NoBottomMarginField = styled.div`
margin-bottom: 0 !important;
`;
const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, label, disabled }) => {
if (branches) {
return (
@@ -56,7 +52,7 @@ const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, l
<label className={classNames("label", "is-size-6")}>{label}</label>
</ZeroflexFieldLabel>
<div className="field-body">
<NoBottomMarginField className={classNames("field", "is-narrow")}>
<div className={classNames("field", "is-narrow", "mb-0")}>
<MinWidthControl className="control">
<Select
className="is-fullwidth"
@@ -67,7 +63,7 @@ const BranchSelector: FC<Props> = ({ branches, onSelectBranch, selectedBranch, l
addValueToOptions={true}
/>
</MinWidthControl>
</NoBottomMarginField>
</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
* SOFTWARE.
*/
import { storiesOf } from "@storybook/react";
import * as React from "react";
import styled from "styled-components";
@@ -30,6 +29,7 @@ import repository from "./__resources__/repository";
// @ts-ignore ignore unknown png
import Git from "./__resources__/git-logo.png";
import { MemoryRouter } from "react-router-dom";
import Icon from "./Icon";
const Wrapper = styled.div`
margin: 2rem;
@@ -42,10 +42,15 @@ const longPath =
"dream-path/src/main/scm-plugins/javaUtilityHomeHousingLinkReferrer/sonia/scm/repositoryUndergroundSupportManager/spi/SvnRepositoryServiceResolver.java";
const baseUrl = "scm-manager.org/scm/repo/hitchhiker/heartOfGold/sources";
const sources = Git;
const prefix = (
<a href="#link">
<Icon name="heart" color="danger" />
</a>
);
storiesOf("BreadCrumb", module)
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>)
.addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator((storyFn) => <Wrapper>{storyFn()}</Wrapper>)
.add("Default", () => (
<Breadcrumb
repository={repository}
@@ -69,4 +74,17 @@ storiesOf("BreadCrumb", module)
revision={"1"}
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`
align-self: center;
/* order actionbar items horizontal */
display: flex;
justify-content: flex-start;
/* ensure space between action bar items */
& > * {
/*
* 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`
border-right: 1px solid lightgray;
margin-right: 0.5rem;
`;
const Breadcrumb: FC<Props> = ({
@@ -176,7 +168,7 @@ const Breadcrumb: FC<Props> = ({
const renderBreadcrumbNav = () => {
let prefixButtons = null;
if (preButtons) {
prefixButtons = <PrefixButton>{preButtons}</PrefixButton>;
prefixButtons = <PrefixButton className="mr-2">{preButtons}</PrefixButton>;
}
let homeUrl = baseUrl + "/";
@@ -227,13 +219,7 @@ const Breadcrumb: FC<Props> = ({
binder.hasExtension<extensionPoints.ReposSourcesEmptyActionbar>("repos.sources.empty.actionbar") &&
sources?._embedded?.children?.length === 0
) {
return (
<ExtensionPoint
name="repos.sources.empty.actionbar"
props={{ repository, sources }}
renderAll={true}
/>
);
return <ExtensionPoint name="repos.sources.empty.actionbar" props={{ repository, sources }} renderAll={true} />;
}
if (binder.hasExtension<extensionPoints.ReposSourcesActionbar>("repos.sources.actionbar")) {
return <ExtensionPoint name="repos.sources.actionbar" props={extProps} renderAll={true} />;
@@ -243,11 +229,15 @@ const Breadcrumb: FC<Props> = ({
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()}
{<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>
<hr className="is-marginless" />
<hr className="m-0" />
</>
);
};

View File

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

View File

@@ -24,7 +24,6 @@
import React, { ReactNode } from "react";
import { Link } from "react-router-dom";
import classNames from "classnames";
import styled from "styled-components";
type Props = {
name: ReactNode;
@@ -36,25 +35,17 @@ type State = {
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> {
constructor(props: Props) {
super(props);
this.state = {
collapsed: false
collapsed: false,
};
}
toggleCollapse = () => {
this.setState(prevState => ({
collapsed: !prevState.collapsed
this.setState((prevState) => ({
collapsed: !prevState.collapsed,
}));
};
@@ -89,13 +80,13 @@ export default class CardColumnGroup extends React.Component<Props, State> {
}
return (
<Container>
<div className="mb-4">
<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)} />
</span>{" "}
{url ? (
<Link to={url} className={"has-text-dark"}>
<Link to={url} className="has-text-dark">
{name}
</Link>
) : (
@@ -103,9 +94,9 @@ export default class CardColumnGroup extends React.Component<Props, State> {
)}
</h2>
<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" />
</Container>
</div>
);
}
}

View File

@@ -35,7 +35,7 @@ const Wrapper = styled.div`
const link = "/foo/bar";
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>;
storiesOf("CardColumnSmall", module)

View File

@@ -35,22 +35,6 @@ type Props = {
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)`
color: inherit;
: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 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;
return (
<StyledLink to={link}>
<div className="media">
{renderAvatar}
<FlexFullHeight className={classNames("media-content", "text-box", "is-flex")}>
<div className="is-flex is-align-items-center">
<ContentLeft>{contentLeft}</ContentLeft>
<ContentRight>{contentRight}</ContentRight>
<div
className={classNames(
"media-content",
"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>
{renderFooter}
</FlexFullHeight>
</div>
</div>
</StyledLink>
);

View File

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

View File

@@ -22,14 +22,15 @@
* SOFTWARE.
*/
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 ErrorPage from "./ErrorPage";
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 Icon from "./Icon";
import styled from "styled-components";
type State = {
error?: Error;
@@ -54,9 +55,6 @@ type ErrorDisplayProps = {
};
const RedirectIconContainer = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 256px;
`;
@@ -69,7 +67,14 @@ const RedirectPage = () => {
<div className="container">
<Title>{t("errorBoundary.redirect.title")}</Title>
<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" />
</RedirectIconContainer>
</div>
@@ -108,7 +113,7 @@ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: Fallb
const fallbackProps = {
error,
errorInfo
errorInfo,
};
return <FallbackComponent {...fallbackProps} />;
@@ -130,7 +135,7 @@ class ErrorBoundary extends React.Component<Props, State> {
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({
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
* SOFTWARE.
*/
import styled from "styled-components";
import * as React from "react";
import { storiesOf } from "@storybook/react";
import Help from "./Help";
const Wrapper = styled.div`
margin: 5rem;
`;
const Spacing = styled.div`
margin-top: 1rem;
`;
const longContent =
"Cleverness nuclear genuine static irresponsibility invited President Zaphod\n" +
"Beeblebrox hyperspace ship. Another custard through computer-generated universe\n" +
@@ -42,17 +32,17 @@ const longContent =
"imaginative generator sweep.";
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("Multiline", () => (
<>
<Spacing>
<div className="mt-4">
<label>With multiline (default):</label>
<Help message={longContent} />
</Spacing>
<Spacing>
</div>
<div className="mt-4">
<label>Without multiline:</label>
<Help message={longContent} multiline={false} />
</Spacing>
</div>
</>
));

View File

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

View File

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

View File

@@ -60,9 +60,9 @@ const OverviewPageActions: FC<Props> = ({
const link = createAbsoluteLink(inputLink);
const groupSelector = groups && (
<div className={"column is-flex"}>
<div className="column is-flex">
<Select
className={"is-fullwidth"}
className="is-fullwidth"
options={groups.map((g) => ({ value: g, label: g }))}
value={currentGroup}
onChange={groupSelected}
@@ -89,9 +89,9 @@ const OverviewPageActions: FC<Props> = ({
};
return (
<div className={"columns is-tablet"}>
<div className="columns is-tablet">
{groupSelector}
<div className={"column"}>
<div className="column">
<FilterInput
placeholder={searchPlaceholder}
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
* SOFTWARE.
*/
import React from "react";
import { Icon } from "@scm-manager/ui-components";
import { storiesOf } from "@storybook/react";
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", () => {
const replacements = [
{
textToReplace: "'",
replacement: <Icon name={"quote-left"} />,
replaceAll: true
replaceAll: true,
},
{
textToReplace: "`",
replacement: <Icon name={"quote-right"} />,
replaceAll: true
}
replaceAll: true,
},
];
return (
<>
<Wrapper>
<div className="m-6">
<SplitAndReplace text={"'So this is it,` said Arthur, 'We are going to die.`"} replacements={replacements} />
</Wrapper>
<Wrapper>
</div>
<div className="m-6">
<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
* SOFTWARE.
*/
import React, { FC, HTMLAttributes } from "react";
import React, { FC } from "react";
import classNames from "classnames";
import { Color, Size } from "./styleConstants";
import styled, { css } from "styled-components";
type Props = {
className?: string;
@@ -39,19 +38,7 @@ type Props = {
onRemove?: () => void;
};
type InnerTagProps = HTMLAttributes<HTMLSpanElement> & {
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 smallClassNames = classNames("p-1", "is-size-7", "has-text-weight-bold");
const Tag: FC<Props> = ({
className,
@@ -82,20 +69,26 @@ const Tag: FC<Props> = ({
return (
<>
<InnerTag
className={classNames("tag", `is-${color}`, `is-${size}`, className, {
<span
className={classNames(
"tag",
`is-${color}`,
`is-${size}`,
className,
{
"is-outlined": outlined,
"is-rounded": rounded,
"has-cursor-pointer": onClick,
})}
"is-clickable": onClick,
},
size === "small" && smallClassNames
)}
title={title}
onClick={onClick}
small={size === "small"}
>
{showIcon}
{label}
{children}
</InnerTag>
</span>
{showDelete}
</>
);

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -37,20 +37,8 @@ type Props = {
errorMessage: string;
};
const StyledLevel = styled(Level)`
align-items: stretch;
margin-bottom: 1rem !important; // same margin as field
`;
const FullWidthInputField = styled(InputField)`
width: 100%;
margin-right: 1.5rem;
`;
const StyledField = styled.div.attrs(() => ({
className: "field"
}))`
align-self: flex-end;
`;
const AddEntryToTableField: FC<Props> = ({
@@ -60,7 +48,7 @@ const AddEntryToTableField: FC<Props> = ({
fieldLabel,
helpText,
validateEntry,
errorMessage
errorMessage,
}) => {
const [entryToAdd, setEntryToAdd] = useState("");
@@ -89,9 +77,11 @@ const AddEntryToTableField: FC<Props> = ({
};
return (
<StyledLevel
<Level
className="is-align-items-stretch mb-4"
children={
<FullWidthInputField
className="mr-5"
label={fieldLabel}
errorMessage={errorMessage}
onChange={handleAddEntryChange}
@@ -103,13 +93,13 @@ const AddEntryToTableField: FC<Props> = ({
/>
}
right={
<StyledField>
<div className="field is-align-self-flex-end">
<AddButton
label={buttonLabel}
action={addButtonClicked}
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
* SOFTWARE.
*/
import styled from "styled-components";
import { storiesOf } from "@storybook/react";
import React from "react";
import AddKeyValueEntryToTableField from "./AddKeyValueEntryToTableField";
import { MemoryRouter } from "react-router-dom";
const Spacing = styled.div`
padding: 2em;
`;
import { storiesOf } from "@storybook/react";
import AddKeyValueEntryToTableField from "./AddKeyValueEntryToTableField";
storiesOf("Forms|AddKeyValueEntryToTableField", module)
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.add("Default", () => (
<Spacing>
<div className="m-6">
<AddKeyValueEntryToTableField
keyFieldLabel="Key"
valueFieldLabel="Value"
buttonLabel="Add to table"
addEntry={(key, value) => {console.log(key, value)}}
buttonLabel="Add to Table"
addEntry={() => null}
/>
</Spacing>
</div>
))
.add("Disabled", () => (
<Spacing>
<div className="m-6">
<AddKeyValueEntryToTableField
keyFieldLabel="Key"
valueFieldLabel="Value"
buttonLabel="Add to table"
addEntry={() => {}}
buttonLabel="Add to Table"
addEntry={() => null}
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
* SOFTWARE.
*/
import React, { FC, useState } from "react";
import styled from "styled-components";
import InputField from "./InputField";
@@ -39,13 +38,7 @@ type Props = {
validateEntry?: (p: string) => boolean;
};
const InputLevel = styled.div`
display: flex;
justify-content: space-between;
`;
const StyledInputField = styled(InputField)`
margin-right: 1.5rem;
const FullWidthInputField = styled(InputField)`
width: 100%;
`;
@@ -62,7 +55,7 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
valueHelpText,
validateEntry,
errorMessage,
addEntry
addEntry,
}) => {
const [key, setKey] = useState("");
const [value, setValue] = useState("");
@@ -77,13 +70,14 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
const add = () => {
addEntry(key, value);
setKey("")
setKey("");
setValue("");
}
};
return (
<InputLevel>
<StyledInputField
<div className="is-flex is-justify-content-space-between">
<FullWidthInputField
className="mr-5"
label={keyFieldLabel}
errorMessage={errorMessage}
onChange={setKey}
@@ -93,7 +87,8 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
disabled={disabled}
helpText={keyHelpText}
/>
<StyledInputField
<FullWidthInputField
className="mr-5"
label={valueFieldLabel}
errorMessage={errorMessage}
onChange={setValue}
@@ -108,7 +103,7 @@ const AddKeyValueEntryToTableField: FC<Props> = ({
action={add}
disabled={disabled || !key || !value || !isValid(key) || !isValid(value)}
/>
</InputLevel>
</div>
);
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,6 @@
* SOFTWARE.
*/
import styled from "styled-components";
import Icon from "../Icon";
import { storiesOf } from "@storybook/react";
import { MemoryRouter } from "react-router-dom";
@@ -31,16 +30,12 @@ import GroupEntry from "./GroupEntry";
import { Button, ButtonGroup } from "../buttons";
import copyToClipboard from "../CopyToClipboard";
const Wrapper = styled.div`
margin: 2rem;
`;
const link = "/foo/bar";
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 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
</strong>
);
@@ -56,7 +51,7 @@ const contentRight = (
storiesOf("GroupEntry", module)
.addDecorator((story) => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator((storyFn) => <Wrapper>{storyFn()}</Wrapper>)
.addDecorator((storyFn) => <div className="m-5">{storyFn()}</div>)
.add("Default", () => (
<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
* SOFTWARE.
*/
import React, { FC, ReactNode } from "react";
import { Link } from "react-router-dom";
import classNames from "classnames";
import styled from "styled-components";
const StyledGroupEntry = styled.div`
max-height: calc(90px - 1.5rem);
width: 100%;
display: flex;
justify-content: space-between;
padding: 0.5rem;
align-items: center;
pointer-events: all;
`;
@@ -49,7 +45,6 @@ const OverlayLink = styled(Link)`
`;
const Avatar = styled.div`
padding-right: 1rem;
.predefined-avatar {
height: 48px;
width: 48px;
@@ -57,12 +52,7 @@ const Avatar = styled.div`
}
`;
const Name = styled.div`
padding: 0 0.25rem;
`;
const Description = styled.p`
padding: 0 0.25rem;
height: 1.5rem;
text-overflow: ellipsis;
overflow-x: hidden;
@@ -72,30 +62,14 @@ const Description = styled.p`
`;
const ContentLeft = styled.div`
display: flex;
flex: 1 1 auto;
align-items: center;
min-width: 0;
`;
const ContentRight = styled.div`
display: flex;
flex: 0 0 auto;
justify-content: flex-end;
pointer-events: all;
padding-left: 2rem;
margin-bottom: -10px;
`;
const NameDescriptionWrapper = styled.div`
overflow: hidden;
flex: 1 1 auto;
`;
const Wrapper = styled.div`
position: relative;
`;
type Props = {
title?: string;
avatar: string | ReactNode;
@@ -107,19 +81,32 @@ type Props = {
const GroupEntry: FC<Props> = ({ link, avatar, title, name, description, contentRight }) => {
return (
<Wrapper>
<div className="is-relative">
<OverlayLink to={link} />
<StyledGroupEntry title={title}>
<ContentLeft>
<Avatar>{avatar}</Avatar>
<NameDescriptionWrapper>
<Name>{name}</Name>
<Description>{description}</Description>
</NameDescriptionWrapper>
<StyledGroupEntry
className={classNames("is-flex", "is-justify-content-space-between", "is-align-items-center", "p-2")}
title={title}
>
<ContentLeft className={classNames("is-flex", "is-flex-grow-1", "is-align-items-center")}>
<Avatar className="mr-4">{avatar}</Avatar>
<div className={classNames("is-flex-grow-1", "is-clipped")}>
<div className="mx-1">{name}</div>
<Description className="mx-1">{description}</Description>
</div>
</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>
</Wrapper>
</div>
);
};

View File

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

View File

@@ -76,7 +76,7 @@ export const Modal: FC<Props> = ({
<div className="modal-background" onClick={closeFunction} />
<SizedModal className="modal-card" size={size}>
<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} />
</header>
<section className="modal-card-body">{body}</section>

View File

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

View File

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

View File

@@ -27,7 +27,6 @@ import Tooltip from "../Tooltip";
const Button = styled.a`
width: 50px;
cursor: pointer;
&:hover {
color: #33b2e8;
}
@@ -47,7 +46,7 @@ const DiffButton: FC<Props> = ({ icon, tooltip, onClick }) => {
return (
<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}`} />
</Button>
</Tooltip>

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,13 +24,11 @@
import React, { FC } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import Tooltip from "../Tooltip";
import Icon from "../Icon";
const Button = styled(Link)`
width: 50px;
cursor: pointer;
&:hover {
color: #33b2e8;
}
@@ -44,7 +42,7 @@ type Props = {
const JumpToFileButton: FC<Props> = ({ link, tooltip }) => {
return (
<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" />
</Button>
</Tooltip>

View File

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

View File

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

View File

@@ -23,7 +23,6 @@
*/
import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { Icon } from "@scm-manager/ui-components";
import Tooltip from "../Tooltip";
@@ -33,10 +32,6 @@ type Props = {
tooltip?: string;
};
const PointerEventsLink = styled(Link)`
pointer-events: all;
`;
class RepositoryEntryLink extends React.Component<Props> {
render() {
const { to, icon, tooltip } = this.props;
@@ -51,9 +46,9 @@ class RepositoryEntryLink extends React.Component<Props> {
}
return (
<PointerEventsLink className="level-item" to={to}>
<Link className="level-item is-clickable" to={to}>
{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
* SOFTWARE.
*/
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 classNames from "classnames";
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 RepositoryFlag from "./RepositoryFlag";
import HealthCheckFailureDetail from "./HealthCheckFailureDetail";
type Props = {
repository: Repository;
@@ -37,11 +37,6 @@ type Props = {
tooltipLocation?: TooltipLocation;
};
const Wrapper = styled.span`
display: flex;
align-items: center;
`;
const RepositoryFlagContainer = styled.div`
.tag {
margin-left: 0.25rem;
@@ -91,13 +86,13 @@ const RepositoryFlags: FC<Props> = ({ repository, className, tooltipLocation = "
);
return (
<Wrapper>
<span className={classNames("is-flex", "is-align-items-center", className)}>
{modal}
<RepositoryFlagContainer>
{repositoryFlags}
<ExtensionPoint name="repository.flags" props={{ repository, tooltipLocation }} renderAll={true} />
</RepositoryFlagContainer>
</Wrapper>
</span>
);
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
import React, { FC } from "react";
import styled from "styled-components";
import classNames from "classnames";
import Icon from "../Icon";
type Props = {
@@ -30,13 +30,8 @@ type Props = {
isVisible: boolean;
};
const IconWithMarginLeft = styled(Icon)`
visibility: ${(props: Props) => (props.isVisible ? "visible" : "hidden")};
margin-left: 0.25em;
`;
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;

View File

@@ -109,7 +109,7 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
<tr>
{React.Children.map(children, (child, index) => (
<th
className={isSortable(child) && "has-cursor-pointer"}
className={isSortable(child) && "is-clickable"}
onClick={isSortable(child) ? () => tableSort(index) : undefined}
onMouseEnter={() => setHoveredColumnIndex(index)}
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 rootElement = usePortalRootElement("toastRoot");
if (!rootElement) {
@@ -74,7 +69,7 @@ const Toast: FC<Props> = ({ children, title, type }) => {
const theme = getTheme(type);
const content = (
<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>
</Container>
);

View File

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

View File

@@ -24,9 +24,7 @@
import React, { FC } from "react";
import styled from "styled-components";
const Buttons = styled.div`
display: flex;
padding-top: 0.5rem;
const FullWidthDiv = styled.div`
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;

View File

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

View File

@@ -110,6 +110,7 @@ footer.footer {
// 6. Import the rest of Bulma
@import "bulma/bulma";
@import "bulma-tooltip/dist/css/bulma-tooltip.min";
@import "bulma-popover/css/bulma-popover";
$dark-75: scale-color($dark, $lightness: 25%);
$dark-50: scale-color($dark, $lightness: 50%);
@@ -194,7 +195,10 @@ $light-25: darken($high-contrast-light-gray, 45%);
}
.has-text-blue-light {
color: $blue-light !important;
color: $blue-light;
}
.has-background-blue-light {
background-color: $blue-light;
}
.has-text-high-contrast-warning {
@@ -286,10 +290,6 @@ $light-25: darken($high-contrast-light-gray, 45%);
background-color: $high-contrast-danger-25;
}
.has-background-blue-light {
background-color: $blue-light;
}
.has-background-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;
}
+.is-delete {
+ .is-delete {
border-color: #aaa;
border-width: 1px 1px 1px 0;
}
@@ -520,7 +520,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
color: #666;
}
.has-border-white {
border-color: #fff !important;
border-color: $white !important;
}
ul.is-separated {
> li:after {
@@ -603,7 +603,7 @@ ul.is-separated {
width: 100%;
td,
th {
border-color: #eee;
border-color: $white-ter;
padding: 1rem;
}
}
@@ -805,7 +805,6 @@ form .field:not(.is-grouped) {
.menu {
div {
height: 100%;
/*border: 1px solid #eee;*/
margin-bottom: 1rem;
}
}
@@ -816,7 +815,7 @@ form .field:not(.is-grouped) {
color: #fff;
font-size: 1em;
font-weight: 600;
background-color: #bbb;
background-color: $blue;
border-radius: 5px 5px 0 0;
padding: 0.5rem 1rem;
text-transform: none;
@@ -826,9 +825,6 @@ form .field:not(.is-grouped) {
margin-bottom: 0;
}
}
.menu div:first-child .menu-label {
background-color: $blue;
}
.menu-list {
a {
color: #333;
@@ -843,7 +839,7 @@ form .field:not(.is-grouped) {
> li {
ul {
margin: 0;
border-top: 1px solid #eee;
border-top: 1px solid $white-ter;
li {
border-right: none;
@@ -866,15 +862,15 @@ form .field:not(.is-grouped) {
}
border-radius: 0;
border-top: 1px solid #eee;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
border-top: 1px solid $white-ter;
border-left: 1px solid $white-ter;
border-right: 1px solid $white-ter;
}
> li:first-child {
border-top: none;
}
li:last-child {
border-bottom: 1px solid #eee;
border-bottom: 1px solid $white-ter;
}
div {
margin-bottom: 0;
@@ -921,7 +917,7 @@ form .field:not(.is-grouped) {
margin-left: 0;
}
// cursor
// NOTE: use .is-clickable instead!
.has-cursor-pointer {
cursor: pointer;
}
@@ -952,5 +948,3 @@ form .field:not(.is-grouped) {
@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 { useUpdateInfo, useVersion } from "@scm-manager/ui-api";
const BottomMarginDiv = styled.div`
margin-bottom: 1.5rem;
`;
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);
`;
const NoBottomMarginSubtitle = styled(Subtitle)`
margin-bottom: 0.25rem !important;
`;
const ImageWrapper = styled.div`
padding: 0.2rem 0.4rem;
`;
@@ -68,7 +60,7 @@ const AdminDetails: FC = () => {
<h3 className="has-text-weight-medium">{t("admin.info.newRelease.title")}</h3>
<p>
{t("admin.info.newRelease.description", {
version: updateInfo?.latestVersion
version: updateInfo?.latestVersion,
})}
</p>
<a className="button is-warning is-pulled-right" target="_blank" href={updateInfo?.link}>
@@ -85,8 +77,8 @@ const AdminDetails: FC = () => {
return (
<>
<Title title={t("admin.info.title")} />
<NoBottomMarginSubtitle subtitle={t("admin.info.currentAppVersion")} />
<BottomMarginDiv>{version}</BottomMarginDiv>
<Subtitle className="mb-1" subtitle={t("admin.info.currentAppVersion")} />
<div className="mb-5">{version}</div>
{updateInfo ? renderUpdateInfo() : null}
<BoxShadowBox className="box">
<article className="media">

View File

@@ -22,6 +22,7 @@
* SOFTWARE.
*/
import * as React from "react";
import classNames from "classnames";
import styled from "styled-components";
type Props = {
@@ -29,15 +30,16 @@ type Props = {
};
const ActionWrapper = styled.div`
justify-content: center;
margin-top: 2em;
padding: 1em 1em;
border: 2px solid #e9f7df;
border: 2px solid #e9f7fd;
`;
export default class PluginBottomActions extends React.Component<Props> {
render() {
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`
margin-bottom: 0 !important;
padding: 0.5rem;
const IconWrapper = styled.span.attrs((props) => ({
className: "level-item mb-0 p-2 is-clickable",
}))`
border: 1px solid #cdcdcd; // $dark-25
border-radius: 4px;
cursor: pointer;
pointer-events: all;
&:hover {
border-color: #9a9a9a; // $dark-50
@@ -78,22 +76,22 @@ const PluginEntry: FC<Props> = ({ plugin, openModal }) => {
const actionBar = () => (
<ActionbarWrapper className="is-flex">
{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" />
</IconWrapper>
)}
{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" />
</IconWrapper>
)}
{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" />
</IconWrapper>
)}
{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" />
</IconWrapper>
)}

View File

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

View File

@@ -23,24 +23,27 @@
*/
import * as React from "react";
import classNames from "classnames";
import styled from "styled-components";
type Props = {
children?: React.Node;
};
const ChildWrapper = styled.div`
justify-content: flex-end;
align-items: center;
`;
export default class PluginTopActions extends React.Component<Props> {
render() {
const { children } = this.props;
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}
</ChildWrapper>
</div>
);
}
}

View File

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

View File

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

View File

@@ -33,28 +33,13 @@ type Props = WithTranslation & {
item?: InfoItem;
};
const BottomMarginA = styled.a`
display: blocK;
margin-bottom: 1.5rem;
`;
const FixedSizedIconWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 160px;
height: 160px;
`;
const LightBlueIcon = styled(Icon)`
margin-bottom: 0.5em;
color: #bff1e6;
`;
const ContentWrapper = styled.div`
min-height: 10.5rem;
margin-left: 1.5em;
`;
class InfoBox extends React.Component<Props> {
@@ -64,7 +49,7 @@ class InfoBox extends React.Component<Props> {
const summary = item ? item.summary : t("login.loading");
return (
<ContentWrapper className={classNames("media-content", "content")}>
<ContentWrapper className={classNames("media-content", "content", "ml-5")}>
<h4 className="has-text-link">{title}</h4>
<p>{summary}</p>
</ContentWrapper>
@@ -75,20 +60,30 @@ class InfoBox extends React.Component<Props> {
const { item, type, t } = this.props;
const icon = type === "plugin" ? "puzzle-piece" : "star";
return (
<BottomMarginA href={item._links.self.href}>
<a className="is-block mb-5" href={item._links.self.href}>
<div className="box media">
<figure className="media-left">
<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.tip")}</div>
</FixedSizedIconWrapper>
</figure>
{this.renderBody()}
</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
* SOFTWARE.
*/
import React, {FormEvent} from "react";
import React, { FormEvent } from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import styled from "styled-components";
import { ErrorNotification, Image, InputField, SubmitButton, UnauthorizedError } from "@scm-manager/ui-components";
@@ -50,7 +50,7 @@ const AvatarImage = styled(Image)`
width: 128px;
height: 128px;
padding: 5px;
background: #fff;
background: white;
border: 1px solid lightgray;
border-radius: 50%;
`;
@@ -60,7 +60,7 @@ class LoginForm extends React.Component<Props, State> {
super(props);
this.state = {
username: "",
password: ""
password: "",
};
}
@@ -73,13 +73,13 @@ class LoginForm extends React.Component<Props, State> {
handleUsernameChange = (value: string) => {
this.setState({
username: value
username: value,
});
};
handlePasswordChange = (value: string) => {
this.setState({
password: value
password: value,
});
};

View File

@@ -114,7 +114,7 @@ class LoginInfo extends React.Component<Props, State> {
createInfoPanel = (info: LoginInfoResponse) => (
<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.plugin} type="plugin" />
</div>

View File

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

View File

@@ -23,15 +23,10 @@
*/
import React, { FC } from "react";
import { Redirect, useLocation } from "react-router-dom";
import styled from "styled-components";
import LoginInfo from "../components/LoginInfo";
import { parse } from "query-string";
import { useIndexLink, useLogin } from "@scm-manager/ui-api";
const HeroSection = styled.section`
padding-top: 2em;
`;
interface FromObject {
from?: string;
}
@@ -62,7 +57,7 @@ const Login: FC = () => {
}
return (
<HeroSection className="hero">
<section className="hero pt-6">
<div className="hero-body">
<div className="container">
<div className="columns is-centered">
@@ -70,7 +65,7 @@ const Login: FC = () => {
</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
* SOFTWARE.
*/
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 {
Button,
Notification as InfoNotification,
@@ -35,26 +45,6 @@ import {
DateFromNow,
devices,
} 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`
min-width: 35rem;
@@ -130,7 +120,7 @@ const NotificationEntry: FC<EntryProps> = ({ notification, removeToast }) => {
}
return (
<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} />
</VerticalCenteredTd>
<DateColumn className="has-text-right">
@@ -143,7 +133,7 @@ const NotificationEntry: FC<EntryProps> = ({ notification, removeToast }) => {
<Icon
name="trash"
color="black"
className="has-cursor-pointer"
className="is-clickable"
title={t("notifications.dismiss")}
onClick={remove}
/>
@@ -172,7 +162,7 @@ const ClearEntry: FC<ClearEntryProps> = ({ notifications, clearToasts }) => {
clearStore();
};
return (
<div className="dropdown-item has-text-centered">
<div className={classNames("dropdown-item", "has-text-centered")}>
<ErrorNotification error={error} />
<DismissAllButton className="is-outlined" color="link" loading={isLoading} action={clear}>
<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);
return (
<div className="dropdown-content p-0">
<table className="table mb-0 card-table">
<div className={classNames("dropdown-content", "p-0")}>
<table className={classNames("table", "card-table", "mb-0")}>
<tbody>
{top.map((n, i) => (
<NotificationEntry key={i} notification={n} removeToast={remove} />
@@ -198,14 +188,18 @@ const NotificationList: FC<Props> = ({ data, clear, remove }) => {
</tbody>
</table>
{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}
{clearLink ? <ClearEntry notifications={data} clearToasts={clear} /> : null}
</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 [t] = useTranslation("commons");
@@ -278,12 +272,8 @@ const NotificationSubscription: FC<SubscriptionProps> = ({ notifications, remove
};
const BellNotificationContainer = styled.div`
position: relative;
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
`;
type NotificationCounterProps = {
@@ -304,8 +294,11 @@ type BellNotificationIconProps = {
const BellNotificationIcon: FC<BellNotificationIconProps> = ({ data, onClick }) => {
const counter = data?._embedded.notifications.length || 0;
return (
<BellNotificationContainer onClick={onClick}>
<Bell iconStyle={counter === 0 ? "far" : "fas"} name="bell" color="white" />
<BellNotificationContainer
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}
</BellNotificationContainer>
);
@@ -357,9 +350,9 @@ const Notifications: FC<NotificationProps> = ({ className }) => {
)}
onClick={(e) => e.stopPropagation()}
>
<Container className="dropdown-trigger">
<div className={classNames("is-flex", "dropdown-trigger", "is-clickable")}>
<BellNotificationIcon data={data} onClick={() => setOpen((o) => !o)} />
</Container>
</div>
<DropDownMenu className="dropdown-menu" id="dropdown-menu" role="menu">
<ErrorBox error={error} />
{isLoading ? <LoadingBox /> : null}

View File

@@ -34,10 +34,6 @@ import SyntaxModal from "../search/SyntaxModal";
import SearchErrorNotification from "../search/SearchErrorNotification";
import queryString from "query-string";
const Field = styled.div`
margin-bottom: 0 !important;
`;
const Input = styled.input`
border-radius: 4px !important;
`;
@@ -77,12 +73,6 @@ const EmptyHits: FC = () => {
const ResultHeading = styled.h3`
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`
@@ -91,8 +81,6 @@ const DropdownMenu = styled.div`
const ResultFooter = styled.div`
border-top: 1px solid lightgray;
margin: 0 0.5rem;
padding: 0.375rem 0.5rem;
`;
const AvatarSection: FC<HitProps> = ({ hit }) => {
@@ -115,7 +103,7 @@ const AvatarSection: FC<HitProps> = ({ hit }) => {
const MoreResults: FC<GotoProps> = ({ gotoDetailSearch }) => {
const [t] = useTranslation("commons");
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">
{t("search.quickSearch.moreResults")}
</Button>
@@ -156,7 +144,18 @@ const Hits: FC<HitsProps> = ({ showHelp, gotoDetailSearch, ...rest }) => {
return (
<>
<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>
<SyntaxHelp onClick={showHelp} />
</ResultHeading>
@@ -331,7 +330,7 @@ const OmniSearch: FC = () => {
const { onKeyDown, index } = useKeyBoardNavigation(gotoDetailSearch, clearQuery, data?._embedded.hits);
return (
<Field className="navbar-item field">
<div className={classNames("navbar-item", "field", "mb-0")}>
{showHelp ? <SyntaxModal close={closeHelp} /> : null}
<div
className={classNames("control", "has-icons-right", {
@@ -376,7 +375,7 @@ const OmniSearch: FC = () => {
</DropdownMenu>
</div>
</div>
</Field>
</div>
);
};

View File

@@ -22,9 +22,10 @@
* SOFTWARE.
*/
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 loadBundle from "./loadBundle";
import styled from "styled-components";
type Props = {
loaded: boolean;
@@ -48,23 +49,11 @@ const BigIcon = styled(Icon)`
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> {
constructor(props: Props) {
super(props);
this.state = {
message: "booting"
message: "booting",
};
}
@@ -72,7 +61,7 @@ class PluginLoader extends React.Component<Props, State> {
const { loaded } = this.props;
if (!loaded) {
this.setState({
message: "loading plugin information"
message: "loading plugin information",
});
this.getPlugins(this.props.link);
@@ -82,16 +71,16 @@ class PluginLoader extends React.Component<Props, State> {
getPlugins = (link: string): Promise<any> => {
return apiClient
.get(link)
.then(response => response.text())
.then((response) => response.text())
.then(JSON.parse)
.then(pluginCollection => pluginCollection._embedded.plugins)
.then((pluginCollection) => pluginCollection._embedded.plugins)
.then(this.loadPlugins)
.then(this.props.callback);
};
loadPlugins = (plugins: Plugin[]) => {
this.setState({
message: "loading plugins"
message: "loading plugins",
});
const promises = [];
@@ -100,21 +89,21 @@ class PluginLoader extends React.Component<Props, State> {
promises.push(this.loadPlugin(plugin));
}
return promises.reduce((chain, current) => {
return chain.then(chainResults => {
return current.then(currentResult => [...chainResults, currentResult]);
return chain.then((chainResults) => {
return current.then((currentResult) => [...chainResults, currentResult]);
});
}, Promise.resolve([]));
};
loadPlugin = (plugin: Plugin) => {
this.setState({
message: `loading ${plugin.name}`
message: `loading ${plugin.name}`,
});
const promises = [];
for (const bundle of plugin.bundles) {
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);
@@ -129,10 +118,17 @@ class PluginLoader extends React.Component<Props, State> {
<section className="section">
<div className="container">
<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" />
<ErrorMessage>{errorMessage}</ErrorMessage>
</Centered>
<div className={classNames("my-5", "is-size-5")}>{errorMessage}</div>
</div>
<ErrorNotification error={error} />
</ErrorBoundary>
</div>

View File

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

View File

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

View File

@@ -23,6 +23,7 @@
*/
import React from "react";
import { Link } from "react-router-dom";
import classNames from "classnames";
import styled from "styled-components";
import { Member } from "@scm-manager/ui-types";
import { Icon } from "@scm-manager/ui-components";
@@ -32,9 +33,6 @@ type Props = {
};
const StyledMember = styled.li`
display: inline-block;
margin-right: 0.25rem;
padding: 0.25rem 0.75rem;
border: 1px solid #eee;
border-radius: 4px;
`;
@@ -59,6 +57,10 @@ export default class GroupMember extends React.Component<Props> {
render() {
const { member } = this.props;
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 classNames from "classnames";
import styled from "styled-components";
import PermissionCheckbox from "./PermissionCheckbox";
import { Loading } from "@scm-manager/ui-components";
import PermissionCheckbox from "./PermissionCheckbox";
type Props = {
permissions: {
@@ -37,8 +37,6 @@ type Props = {
};
const StyledWrapper = styled.div`
padding-bottom: 0;
& .field .control {
width: 100%;
word-wrap: break-word;
@@ -56,7 +54,7 @@ export default class PermissionsWrapper extends React.Component<Props> {
const permissionArray = Object.keys(permissions);
return (
<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 => (
<PermissionCheckbox
key={p}

View File

@@ -22,49 +22,34 @@
* SOFTWARE.
*/
import React from "react";
import { Branch, Repository } from "@scm-manager/ui-types";
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 DefaultBranchTag from "./DefaultBranchTag";
import { DateFromNow } from "@scm-manager/ui-components";
import styled from "styled-components";
type Props = WithTranslation & {
repository: Repository;
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> {
render() {
const { repository, branch, t } = this.props;
return (
<div className="media">
<FlexRow className="media-content subtitle">
<Label>{t("branch.name")}</Label> {branch.name} <DefaultBranchTag defaultBranch={branch.defaultBranch} />
<Created className="is-ellipsis-overflow">
{t("branches.overview.lastCommit")} <Date date={branch.lastCommitDate} className="has-text-grey" />
</Created>
</FlexRow>
<div
className={classNames("media-content", "subtitle", "is-flex", "is-align-items-center", "is-flex-wrap-wrap")}
>
<strong className="mr-1">{t("branch.name")}</strong> {branch.name}{" "}
<DefaultBranchTag defaultBranch={branch.defaultBranch} />
<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">
<BranchButtonGroup repository={repository} branch={branch} />
</div>

View File

@@ -23,11 +23,11 @@
*/
import React, { FC } from "react";
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 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 = {
baseUrl: string;
@@ -35,11 +35,6 @@ type Props = {
onDelete: (branch: Branch) => void;
};
const Created = styled.span`
margin-left: 1rem;
font-size: 0.8rem;
`;
const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => {
const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`;
const [t] = useTranslation("repos");
@@ -63,9 +58,9 @@ const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => {
<DefaultBranchTag defaultBranch={branch.defaultBranch} />
</ReactLink>
{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} />
</Created>
</span>
)}
</td>
<td className="is-darker">{deleteButton}</td>

View File

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

View File

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

View File

@@ -22,14 +22,15 @@
* SOFTWARE.
*/
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 { 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 { ErrorNotification, FilterInput, Help, Icon, Loading } from "@scm-manager/ui-components";
import CodeActionBar from "../components/CodeActionBar";
import styled from "styled-components";
import FileSearchResults from "../components/FileSearchResults";
import { useTranslation } from "react-i18next";
import { filepathSearch } from "../utils/filepathSearch";
type Props = {
@@ -43,27 +44,14 @@ type Params = {
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)`
border-right: 1px solid lightgray;
margin-right: 0.75rem;
padding-right: 0.75em;
`;
const HomeIcon = styled(Icon)`
line-height: 1.5rem;
`;
const SearchHelp = styled(Help)`
margin-left: 0.75rem;
`;
const useRevision = () => {
const { revision } = useParams<Params>();
return revision;
@@ -113,8 +101,18 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch }
switchViewLink={evaluateSwitchViewLink}
/>
<div className="panel">
<InputContainer>
<HomeLink to={contentBaseUrl}>
<div
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" />
</HomeLink>
<FilterInput
@@ -124,8 +122,8 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch }
filter={search}
autoFocus={true}
/>
<SearchHelp message={t("filesearch.input.help")} />
</InputContainer>
<Help className="ml-3" message={t("filesearch.input.help")} />
</div>
<ErrorNotification error={error} />
{isLoading ? <Loading /> : <FileSearchResults contentBaseUrl={contentBaseUrl} query={query} paths={result} />}
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,9 +51,9 @@ const HealthCheckWarning: FC<Props> = ({ repository }) => {
return (
<Notification type="danger">
{modal}
<div className={"has-cursor-pointer"} onClick={() => setShowHealthCheck(true)}>
<div className="is-clickable" onClick={() => setShowHealthCheck(true)}>
<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>
</Notification>
);

View File

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

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
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 { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
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";
const TagGroup = styled.span`
font-weight: bold;
& > * {
margin-right: 0.25rem;
}
@@ -159,7 +158,7 @@ const RepositoryRoot = () => {
const titleComponent = (
<>
<RouteLink to={`/repos/${repository.namespace}/`} className={"has-text-dark"}>
<RouteLink to={`/repos/${repository.namespace}/`} className="has-text-dark">
{repository.namespace}
</RouteLink>
/{repository.name}
@@ -220,7 +219,7 @@ const RepositoryRoot = () => {
afterTitle={
<div className="is-flex">
<ExtensionPoint name={"repository.afterTitle"} props={{ repository }} />
<TagGroup>
<TagGroup className="has-text-weight-bold">
<RepositoryFlags repository={repository} tooltipLocation="bottom" />
</TagGroup>
</div>

View File

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

View File

@@ -24,7 +24,6 @@
import React, { FC } from "react";
import { File } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, MarkdownView } from "@scm-manager/ui-components";
import styled from "styled-components";
import replaceBranchWithRevision from "../../ReplaceBranchWithRevision";
import { useLocation } from "react-router-dom";
import { useFileContent } from "@scm-manager/ui-api";
@@ -34,10 +33,6 @@ type Props = {
basePath: string;
};
const MarkdownContent = styled.div`
padding: 0.5rem;
`;
const MarkdownViewer: FC<Props> = ({ file, basePath }) => {
const { isLoading, error, data: content } = useFileContent(file);
const location = useLocation();
@@ -53,9 +48,9 @@ const MarkdownViewer: FC<Props> = ({ file, basePath }) => {
const permalink = replaceBranchWithRevision(location.pathname, file.revision);
return (
<MarkdownContent>
<div className="p-2">
<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
* SOFTWARE.
*/
import React, {FC, useState} from "react";
import React, { FC, useState } from "react";
import styled from "styled-components";
import MarkdownViewer from "./MarkdownViewer";
import SourcecodeViewer from "./SourcecodeViewer";
import {File} from "@scm-manager/ui-types";
import {Button} from "@scm-manager/ui-components";
import {useTranslation} from "react-i18next";
import { File } from "@scm-manager/ui-types";
import { Button } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
const ToggleButton = styled(Button)`
max-width: 1rem;
@@ -37,17 +37,13 @@ const ToggleButton = styled(Button)`
z-index: 30;
`;
const Container = styled.div`
position: relative;
`;
type Props = {
file: File;
basePath: string;
};
const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => {
const {t} = useTranslation("repos");
const SwitchableMarkdownViewer: FC<Props> = ({ file, basePath }) => {
const { t } = useTranslation("repos");
const [renderMarkdown, setRenderMarkdown] = useState(true);
const toggleMarkdown = () => {
@@ -55,7 +51,7 @@ const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => {
};
return (
<Container>
<div className="is-relative">
<ToggleButton
color={renderMarkdown ? "link" : ""}
action={toggleMarkdown}
@@ -65,11 +61,14 @@ const SwitchableMarkdownViewer: FC<Props> = ({file, basePath}) => {
: t("sources.content.toggleButton.showMarkdown")
}
>
<i className="fab fa-markdown"/>
<i className="fab fa-markdown" />
</ToggleButton>
{renderMarkdown ? <MarkdownViewer file={file} basePath={basePath}/> :
<SourcecodeViewer file={file} language={"MARKDOWN"}/>}
</Container>
{renderMarkdown ? (
<MarkdownViewer file={file} basePath={basePath} />
) : (
<SourcecodeViewer file={file} language={"MARKDOWN"} />
)}
</div>
);
};

View File

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

View File

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

View File

@@ -21,12 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
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 styled from "styled-components";
import { DateFromNow, Icon } from "@scm-manager/ui-components";
type Props = {
@@ -36,11 +35,6 @@ type Props = {
// deleting: boolean;
};
const Created = styled.span`
margin-left: 1rem;
font-size: 0.8rem;
`;
const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => {
const [t] = useTranslation("repos");
@@ -61,9 +55,9 @@ const TagRow: FC<Props> = ({ tag, baseUrl, onDelete }) => {
<td>
<RouterLink to={to} title={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} />
</Created>
</span>
</RouterLink>
</td>
<td className="is-darker">{deleteButton}</td>

View File

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

View File

@@ -22,24 +22,18 @@
* SOFTWARE.
*/
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 {
Button,
ErrorNotification,
Level,
Modal,
PasswordConfirmation,
SubmitButton
SubmitButton,
} 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 = {
user: User;
@@ -53,12 +47,12 @@ const UserConverter: FC<Props> = ({ user }) => {
const {
isLoading: isConvertingToInternal,
error: convertingToInternalError,
convertToInternal
convertToInternal,
} = useConvertToInternal();
const {
isLoading: isConvertingToExternal,
error: convertingToExternalError,
convertToExternal
convertToExternal,
} = useConvertToExternal();
const error = convertingToExternalError || convertingToInternalError || undefined;
const isLoading = isConvertingToExternal || isConvertingToInternal;
@@ -135,7 +129,9 @@ const UserConverter: FC<Props> = ({ user }) => {
{showPasswordModal && passwordModal}
{error && <ErrorNotification error={error} />}
<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">
<Level right={getConvertButton()} />
</div>

View File

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