Apply new design for AheadBehindTag

Committed-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
Co-authored-by: tarik-guersoy <tarik.guersoy@cloudogu.com>
Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2023-08-09 12:49:00 +02:00
parent 33e8d30b62
commit 7d47c9cc94
18 changed files with 3062 additions and 3214 deletions

View File

@@ -0,0 +1,2 @@
- type: changed
description: Apply new design to ahead behind tag

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
import React, { ComponentType, ReactNode } from "react";
import React, { ComponentType, FC, ReactNode } from "react";
import {
Branch,
Changeset,
@@ -49,6 +49,7 @@ import {
import { ExtensionPointDefinition } from "./binder";
import { RenderableExtensionPointDefinition, SimpleRenderableDynamicExtensionPointDefinition } from "./ExtensionPoint";
import ExtractProps from "./extractProps";
import { useGeneratedId } from "@scm-manager/ui-components";
type RepositoryCreatorSubFormProps = {
repository: RepositoryCreation;
@@ -684,17 +685,22 @@ export type GroupInformationTableBottom = RenderableExtensionPointDefinition<
type BranchListDetailProps = {
repository: Repository;
branch: Branch;
labelId: string;
};
/**
* @since 2.46.0
* @example
* ```typescript
* import { Card } from "@scm-manager/ui-layout";
* import { extensionPoints, binder } from "@scm-manager/ui-extensions";
*
* binder.bind<extensionPoints.BranchListDetail>(
* "branches.list.detail",
* <Card.Details.Detail>
* <Card.Details.Detail.Label>Reviewers</Card.Details.Detail.Label>
* <Card.Details.Detail.Tag>2 / 3</Card.Details.Detail.Tag>
* </Card.Details.Detail>
* )
* ```
*/
export type BranchListDetail = ExtensionPointDefinition<
"branches.list.detail",
{
name: string;
render: (props: BranchListDetailProps) => ReactNode | null;
},
Omit<BranchListDetailProps, "labelId">
>;
export type BranchListDetail = RenderableExtensionPointDefinition<"branches.list.detail", BranchListDetailProps>;

View File

@@ -24,7 +24,11 @@
export { default as binder, Binder, ExtensionPointDefinition } from "./binder";
export * from "./useBinder";
export { default as ExtensionPoint } from "./ExtensionPoint";
export {
default as ExtensionPoint,
RenderableExtensionPointDefinition,
SimpleRenderableDynamicExtensionPointDefinition,
} from "./ExtensionPoint";
export { default as ExtractProps } from "./extractProps";
// suppress eslint prettier warning,

View File

@@ -17,8 +17,6 @@
"@scm-manager/prettier-config": "^2.10.1",
"@scm-manager/tsconfig": "^2.13.0",
"@scm-manager/ui-styles": "2.45.3-SNAPSHOT",
"@scm-manager/ui-overlays": "2.45.3-SNAPSHOT",
"@scm-manager/ui-buttons": "2.45.3-SNAPSHOT",
"@storybook/addon-actions": "^6.5.10",
"@storybook/addon-docs": "^6.5.14",
"@storybook/addon-essentials": "^6.5.10",
@@ -39,7 +37,8 @@
"react-dom": "17",
"styled-components": "5",
"@scm-manager/ui-buttons": "2.45.3-SNAPSHOT",
"@scm-manager/ui-overlays": "2.45.3-SNAPSHOT"
"@scm-manager/ui-overlays": "2.45.3-SNAPSHOT",
"@scm-manager/ui-components": "2.45.3-SNAPSHOT"
},
"prettier": "@scm-manager/prettier-config",
"eslintConfig": {

View File

@@ -32,6 +32,7 @@ import CardTitle from "../card/CardTitle";
import { Menu } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
import CardRow from "../card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "../card/CardDetail";
export default {
title: "CardList",
@@ -140,5 +141,30 @@ Default.args = {
<span>(OPEN)</span>
</CardRow>
</CardListCard>,
<CardListCard>
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My least liked repo
</Link>
</CardTitle>
<CardRow>
<CardDetails>
<CardDetail>
{({ labelId }) => (
<>
<CardDetailLabel id={labelId}>Workers</CardDetailLabel>
<CardDetailTag aria-labelledby={labelId}>2/3</CardDetailTag>
</>
)}
</CardDetail>
<CardDetail>
<CardDetailLabel>MyCustomDetail</CardDetailLabel>
<a className="is-relative" href="https://scm-manager.org">
Docs
</a>
</CardDetail>
</CardDetails>
</CardRow>
</CardListCard>,
],
} as ComponentProps<typeof CardListBox>;

View File

@@ -30,6 +30,7 @@ import Card from "../card/Card";
/**
* @beta
* @since 2.44.0
* @see Card
*/
export const CardListCard = React.forwardRef<HTMLElement, Omit<ComponentProps<typeof Card>, "as">>((props, ref) => (
<Card ref={ref} {...props} as="li" />

View File

@@ -0,0 +1,72 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import StoryRouter from "storybook-react-router";
import { ComponentMeta, StoryFn } from "@storybook/react";
import React, { ComponentProps } from "react";
import { ExtractProps } from "@scm-manager/ui-extensions";
import { Link } from "react-router-dom";
import CardTitle from "../card/CardTitle";
import CardRow from "../card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "./CardDetail";
import Card from "./Card";
export default {
title: "Card",
component: Card,
decorators: [StoryRouter()],
} as ComponentMeta<typeof Card>;
const Template: StoryFn<ExtractProps<typeof Card>> = (args) => <Card {...args} />;
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {
className: "box",
children: [
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My least liked repo
</Link>
</CardTitle>,
<CardRow>
<CardDetails>
<CardDetail>
{({ labelId }) => (
<>
<CardDetailLabel id={labelId}>Workers</CardDetailLabel>
<CardDetailTag aria-labelledby={labelId}>2/3</CardDetailTag>
</>
)}
</CardDetail>
<CardDetail>
<CardDetailLabel>MyCustomDetail</CardDetailLabel>
<a className="is-relative" href="https://scm-manager.org">
Docs
</a>
</CardDetail>
</CardDetails>
</CardRow>,
],
} as ComponentProps<typeof Card>;

View File

@@ -46,6 +46,9 @@ type Props = HTMLAttributes<HTMLElement> & {
};
/**
* If the Card's title contains a link, the whole Card becomes a click target for that link.
* Because of this, any interactive elements require the <code>is-relative</code> class to receive cursor events.
*
* @beta
* @since 2.44.0
*/

View File

@@ -0,0 +1,91 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { HTMLAttributes, ReactNode } from "react";
import classNames from "classnames";
import { useGeneratedId } from "@scm-manager/ui-components";
import styled from "styled-components";
type CardDetailProps = HTMLAttributes<HTMLSpanElement> & {
children: ReactNode | ((props: { labelId: string }) => ReactNode);
};
/**
* @beta
* @since 2.46.0
*/
export const CardDetail = React.forwardRef<HTMLSpanElement, CardDetailProps>(
({ children, className, ...props }, ref) => {
const labelId = useGeneratedId();
return (
<span {...props} className={classNames("is-flex is-align-items-center", className)} ref={ref}>
{typeof children === "function" ? children({ labelId }) : children}
</span>
);
}
);
/**
* @beta
* @since 2.46.0
*/
export const CardDetailLabel = React.forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(
({ children, className, ...props }, ref) => (
<span {...props} className={classNames("has-text-secondary is-size-7 mr-1", className)} ref={ref}>
{children}
</span>
)
);
/**
* @beta
* @since 2.46.0
*/
export const CardDetailTag = React.forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(
({ children, className, ...props }, ref) => (
<span {...props} className={classNames("tag is-rounded is-light", className)} ref={ref}>
{children}
</span>
)
);
const CardDetailsRowContainer = styled.div`
gap: 0.5rem 1rem;
`;
/**
* @beta
* @since 2.46.0
*/
export const CardDetails = React.forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ children, className, ...props }, ref) => (
<CardDetailsRowContainer
{...props}
className={classNames("is-flex is-flex-wrap-wrap is-align-items-center", className)}
ref={ref}
>
{children}
</CardDetailsRowContainer>
)
);

View File

@@ -25,15 +25,27 @@
import CardListComponent, { CardListBox as CardListBoxComponent, CardListCard } from "./card-list/CardList";
import CardTitle from "./card/CardTitle";
import CardRow from "./card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "./card/CardDetail";
import CardComponent from "./card/Card";
export { default as Collapsible } from "./collapsible/Collapsible";
const CardListExport = {
Card: Object.assign(CardListCard, {
Row: CardRow,
Title: CardTitle,
const CardExport = {
Title: CardTitle,
Row: CardRow,
Details: Object.assign(CardDetails, {
Detail: Object.assign(CardDetail, {
Label: CardDetailLabel,
Tag: CardDetailTag,
}),
}),
};
export const Card = Object.assign(CardComponent, CardExport);
const CardListExport = {
Card: Object.assign(CardListCard, CardExport),
};
export const CardList = Object.assign(CardListComponent, CardListExport);
export const CardListBox = Object.assign(CardListBoxComponent, CardListExport);

View File

@@ -158,8 +158,9 @@
"dangerZone": "Branch löschen",
"aheadBehind": {
"tooltip": "{{ahead}} Commit(s) vor, {{behind}} Commit(s) hinter dem Default Branch",
"aheadLabel": "{{count}} voraus",
"behindLabel": "{{count}} hinterher"
"label": "Voraus / Hinterher",
"ahead": "Voraus",
"behind": "Hinterher"
},
"delete": {
"button": "Löschen",

View File

@@ -158,8 +158,9 @@
"dangerZone": "Delete Branch",
"aheadBehind": {
"tooltip": "{{ahead}} commit(s) ahead, {{behind}} commit(s) behind default branch",
"aheadLabel": "{{count}} ahead",
"behindLabel": "{{count}} behind"
"label": "Ahead / Behind",
"ahead": "Ahead",
"behind": "Behind"
},
"delete": {
"button": "Delete",

View File

@@ -26,92 +26,43 @@ import { Branch, BranchDetails } from "@scm-manager/ui-types";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { Tooltip } from "@scm-manager/ui-components";
import { calculateBarLength } from "./aheadBehind";
import { Tooltip } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
type Props = {
branch: Branch;
details: BranchDetails;
hiddenMobile?: boolean;
verbose?: boolean;
details?: BranchDetails;
labelId?: string
};
type BarProps = {
width: number;
direction: "right" | "left";
};
const Ahead = styled.span`
border-left: 1px solid gray;
`;
const Behind = styled.span``;
const Count = styled.span`
word-break: keep-all;
width: max-content;
`;
const Bar = styled.span.attrs<BarProps>((props) => ({
style: {
width: props.width + "%",
borderRadius: props.direction === "left" ? "25px 0 0 25px" : "0 25px 25px 0",
},
}))<BarProps>`
height: 3px;
max-width: 100%;
margin-top: -2px;
margin-bottom: 2px;
`;
const TooltipWithDefaultCursor = styled(Tooltip)`
cursor: default !important;
`;
const AheadBehindTag: FC<Props> = ({ branch, details, hiddenMobile, verbose }) => {
const AheadBehindTag: FC<Props> = ({ branch, details, labelId }) => {
const [t] = useTranslation("repos");
if (
branch.defaultBranch ||
!details ||
typeof details.changesetsBehind !== "number" ||
typeof details.changesetsAhead !== "number"
) {
return null;
}
const behindText = verbose
? t("branch.aheadBehind.behindLabel", { count: details.changesetsBehind })
: details.changesetsBehind;
const aheadText = verbose
? t("branch.aheadBehind.aheadLabel", { count: details.changesetsAhead })
: details.changesetsAhead;
return (
<div className={`is-flex is-justify-content-center is-unselectable my-1 ${hiddenMobile ? "is-hidden-mobile" : ""}`}>
<TooltipWithDefaultCursor
message={t("branch.aheadBehind.tooltip", { ahead: details.changesetsAhead, behind: details.changesetsBehind })}
location="top"
>
<div className="is-flex">
<Behind className="is-flex is-flex-direction-column is-align-items-flex-end p-0">
<Count className="is-size-7 pr-1">{behindText}</Count>
<Bar
className="has-rounded-border-left has-background-secondary"
width={calculateBarLength(details.changesetsBehind)}
direction="left"
/>
</Behind>
<Ahead className="is-flex is-flex-direction-column is-align-items-flex-start p-0">
<Count className="is-size-7 pl-1">{aheadText}</Count>
<Bar
className="has-rounded-border-right has-background-secondary"
width={calculateBarLength(details.changesetsAhead)}
direction="right"
/>
</Ahead>
</div>
</TooltipWithDefaultCursor>
</div>
<Tooltip
message={t("branch.aheadBehind.tooltip", { ahead: details.changesetsAhead, behind: details.changesetsBehind })}
>
<span aria-labelledby={labelId} className="is-inline-flex is-align-items-center">
<Icon className={details.changesetsAhead > 0 ? "has-text-success" : "has-text-grey-light"}>arrow-up</Icon>
<Count aria-label={t("branch.aheadBehind.ahead")} className="is-size-7 pl-0">{details.changesetsAhead}</Count>
<Icon className={details.changesetsBehind > 0 ? "has-text-warning" : "has-text-grey-light"}>arrow-down</Icon>
<Count aria-label={t("branch.aheadBehind.behind")} className="is-size-7 pr-1">{details.changesetsBehind}</Count>
</span>
</Tooltip>
);
};

View File

@@ -25,7 +25,7 @@ import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import { Branch, Repository } from "@scm-manager/ui-types";
import { SmallLoadingSpinner, Subtitle } from "@scm-manager/ui-components";
import {SmallLoadingSpinner, Subtitle, useGeneratedId} from "@scm-manager/ui-components";
import BranchButtonGroup from "./BranchButtonGroup";
import DefaultBranchTag from "./DefaultBranchTag";
import AheadBehindTag from "./AheadBehindTag";
@@ -41,12 +41,12 @@ type Props = {
const BranchDetail: FC<Props> = ({ repository, branch }) => {
const [t] = useTranslation("repos");
const { data, isLoading } = useBranchDetails(repository, branch);
const labelId = useGeneratedId();
let aheadBehind;
if (isLoading) {
aheadBehind = <SmallLoadingSpinner />;
} else if (data) {
aheadBehind = <AheadBehindTag branch={branch} details={data} verbose={true} />;
aheadBehind = <AheadBehindTag branch={branch} details={data} labelId={labelId} />;
} else {
aheadBehind = null;
}
@@ -77,7 +77,7 @@ const BranchDetail: FC<Props> = ({ repository, branch }) => {
<BranchButtonGroup repository={repository} branch={branch} />
</div>
</div>
{aheadBehind}
<span id={labelId} className="is-size-7 has-text-secondary">{t("branch.aheadBehind.label")}</span>{aheadBehind}
</>
);
};

View File

@@ -23,8 +23,7 @@
*/
import { Branch, BranchDetails, Repository } from "@scm-manager/ui-types";
import React, { FC, useMemo } from "react";
import { extensionPoints } from "@scm-manager/ui-extensions";
import React, { FC } from "react";
import { CardList } from "@scm-manager/ui-layout";
import { ErrorNotification } from "@scm-manager/ui-components";
import { useDeleteBranch } from "@scm-manager/ui-api";
@@ -39,7 +38,6 @@ type Props = {
const BranchList: FC<Props> = ({ repository, baseUrl, branches, branchesDetails }) => {
const { isLoading, error, remove } = useDeleteBranch(repository);
const defaultBranchDetails = useMemo<extensionPoints.BranchListDetail["type"][]>(() => [], []);
return (
<>
@@ -49,12 +47,11 @@ const BranchList: FC<Props> = ({ repository, baseUrl, branches, branchesDetails
<BranchListItem
key={branch.name}
branch={branch}
defaultBranchDetails={defaultBranchDetails}
remove={remove}
isLoading={isLoading}
baseUrl={baseUrl}
repository={repository}
branchDetails={branchesDetails?.find(({ branchName }) => branchName === branch.name)}
branchesDetails={branchesDetails}
/>
))}
</CardList>

View File

@@ -24,53 +24,29 @@
import { Dialog, Menu } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
import { CardList } from "@scm-manager/ui-layout";
import { CardList, Card } from "@scm-manager/ui-layout";
import { Link } from "react-router-dom";
import { encodePart } from "../../sources/components/content/FileLink";
import { useKeyboardIteratorTarget } from "@scm-manager/ui-shortcuts";
import { Trans, useTranslation } from "react-i18next";
import { DateFromNow, useGeneratedId } from "@scm-manager/ui-components";
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
import { DateFromNow } from "@scm-manager/ui-components";
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
import React, { FC } from "react";
import { Branch, BranchDetails, Repository } from "@scm-manager/ui-types";
import styled from "styled-components";
const DetailsContainer = styled(CardList.Card.Row)`
gap: 0.5rem 1rem;
`;
const BranchDetail: FC<{
branch: Branch;
repository: Repository;
detail: extensionPoints.BranchListDetail["type"];
}> = ({ repository, detail, branch }) => {
const labelId = useGeneratedId();
const renderedDetail = detail.render({ branch, repository, labelId });
if (!renderedDetail) {
return null;
}
return (
<span key={detail.name}>
<span className="has-text-secondary mr-1" id={labelId}>
{detail.name}
</span>
{renderedDetail}
</span>
);
};
import AheadBehindTag from "./AheadBehindTag";
type Props = {
branch: Branch;
defaultBranchDetails: extensionPoints.BranchListDetail["type"][];
remove: (branch: Branch) => void;
isLoading: boolean;
baseUrl: string;
repository: Repository;
branchDetails?: BranchDetails;
branchesDetails?: BranchDetails[];
};
const BranchListItem: FC<Props> = ({ branch, defaultBranchDetails, remove, isLoading, baseUrl, repository }) => {
const binder = useBinder();
const BranchListItem: FC<Props> = ({ branch, remove, isLoading, branchesDetails, baseUrl, repository }) => {
const [t] = useTranslation("repos");
return (
@@ -98,14 +74,14 @@ const BranchListItem: FC<Props> = ({ branch, defaultBranchDetails, remove, isLoa
) : undefined
}
>
<CardList.Card.Row className="is-flex">
<CardList.Card.Title>
<Card.Row className="is-flex">
<Card.Title>
<Link to={`${baseUrl}/${encodePart(branch.name)}/info`} ref={useKeyboardIteratorTarget()}>
{branch.name}
</Link>
</CardList.Card.Title>
</CardList.Card.Row>
<CardList.Card.Row className="is-flex is-flex-wrap-wrap is-size-7 has-text-secondary is-white-space-pre">
</Card.Title>
</Card.Row>
<Card.Row className="is-flex is-flex-wrap-wrap is-size-7 has-text-secondary is-white-space-pre">
<Trans
t={t}
i18nKey="branches.table.branches.subtitle"
@@ -117,18 +93,33 @@ const BranchListItem: FC<Props> = ({ branch, defaultBranchDetails, remove, isLoa
space: <span />,
}}
/>
</CardList.Card.Row>
<DetailsContainer className="is-flex is-flex-wrap-wrap is-align-items-center">
{[
...defaultBranchDetails,
...binder.getExtensions<extensionPoints.BranchListDetail>("branches.list.detail", {
branch,
repository,
}),
].map((detail) => (
<BranchDetail key={detail.name} branch={branch} detail={detail} repository={repository} />
))}
</DetailsContainer>
</Card.Row>
<Card.Row>
<Card.Details>
<Card.Details.Detail>
{({ labelId }) => (
<>
<Card.Details.Detail.Label id={labelId}>
{branch.defaultBranch ? null : t("branch.aheadBehind.label")}
</Card.Details.Detail.Label>
<AheadBehindTag
branch={branch}
details={branchesDetails?.find(({ branchName }) => branchName === branch.name)}
labelId={labelId}
/>
</>
)}
</Card.Details.Detail>
<ExtensionPoint<extensionPoints.BranchListDetail>
name="branches.list.detail"
props={{
branch,
repository,
}}
renderAll
/>
</Card.Details>
</Card.Row>
</CardList.Card>
);
};

View File

@@ -40,6 +40,10 @@ const BranchListWrapper = styled.div`
gap: 1rem;
`;
const HeaderWrapper = styled.div`
gap: 0.5rem 1rem;
`;
type Props = {
repository: Repository;
baseUrl: string;
@@ -70,27 +74,25 @@ const BranchTableWrapper: FC<Props> = ({ repository, baseUrl, data }) => {
<>
<Subtitle subtitle={t("branches.overview.title")} />
<ErrorNotification error={error} />
<div className="is-flex mb-3 is-justify-content-space-between">
<div>
<div className="is-flex is-align-items-center mb-3">
<label className="mr-2" htmlFor="branches-overview-sort">
{t("branches.overview.sort.label")}
</label>
<Select id="branches-overview-sort" onChange={(e) => setSort(e.target.value as SortOption)}>
{SORT_OPTIONS.map((sortOption) => (
<option key={sortOption} value={sortOption}>
{t(`branches.overview.sort.option.${sortOption}`)}
</option>
))}
</Select>
</div>
<HeaderWrapper className="is-flex is-flex-wrap-wrap is-justify-content-space-between mb-3">
<div className="is-flex is-align-items-center">
<label className="mr-2" htmlFor="branches-overview-sort">
{t("branches.overview.sort.label")}
</label>
<Select id="branches-overview-sort" onChange={(e) => setSort(e.target.value as SortOption)}>
{SORT_OPTIONS.map((sortOption) => (
<option key={sortOption} value={sortOption}>
{t(`branches.overview.sort.option.${sortOption}`)}
</option>
))}
</Select>
</div>
{showCreateButton ? (
<LinkButton variant="primary" to="./create">
{t("branches.overview.createButton")}
</LinkButton>
) : null}
</div>
</HeaderWrapper>
<BranchListWrapper className="is-flex is-flex-direction-column">
<KeyboardIterator>
{activeBranches.length > 0 ? (

5797
yarn.lock

File diff suppressed because it is too large Load Diff