mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
Create atomic design page template for master-detail views
This commit is contained in:
2
gradle/changelog/data_page_template.yaml
Normal file
2
gradle/changelog/data_page_template.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Atomic design page template simple data pages
|
||||
@@ -27,7 +27,7 @@ import classNames from "classnames";
|
||||
type NotificationType = "primary" | "info" | "success" | "warning" | "danger" | "inherit";
|
||||
|
||||
type Props = {
|
||||
type: NotificationType;
|
||||
type?: NotificationType;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
"@scm-manager/tsconfig": "^2.13.0",
|
||||
"@scm-manager/ui-styles": "2.46.2-SNAPSHOT",
|
||||
"@scm-manager/ui-overlays": "2.46.2-SNAPSHOT",
|
||||
"@scm-manager/ui-forms": "2.46.2-SNAPSHOT",
|
||||
"@storybook/addon-actions": "^6.5.10",
|
||||
"@storybook/addon-docs": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.10",
|
||||
@@ -48,6 +49,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.1",
|
||||
"@scm-manager/ui-buttons": "2.46.2-SNAPSHOT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
scm-ui/ui-layout/src/_helpers/with-classes.tsx
Normal file
52
scm-ui/ui-layout/src/_helpers/with-classes.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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, { ComponentProps, ElementRef, ForwardRefExoticComponent, ReactElement } from "react";
|
||||
import classNames from "classnames";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
|
||||
const withClasses = <Element extends React.ElementType | ForwardRefExoticComponent<any>>(
|
||||
typ: Element,
|
||||
additionalClassNames: string[],
|
||||
additionalProps?: Partial<ComponentProps<Element>>
|
||||
) =>
|
||||
React.forwardRef<
|
||||
ElementRef<Element>,
|
||||
| (ComponentProps<Element> & { asChild?: false; className?: string })
|
||||
| { asChild: true; children: ReactElement<{ className?: string }> }
|
||||
>((props, ref) => {
|
||||
// @ts-ignore Typescript doesn't get it for some reason
|
||||
if (props.asChild) {
|
||||
return <Slot {...props} className={classNames(...additionalClassNames)} />;
|
||||
} else {
|
||||
return React.createElement(typ, {
|
||||
...additionalProps,
|
||||
...props,
|
||||
className: classNames((props as any).className, ...additionalClassNames),
|
||||
ref,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default withClasses;
|
||||
@@ -59,7 +59,7 @@ const Card = React.forwardRef<HTMLElement, Props>(
|
||||
},
|
||||
avatar ? avatar : null,
|
||||
<div
|
||||
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-clipped"
|
||||
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
|
||||
style={{ gap: rowGap }}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -39,7 +39,7 @@ 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}>
|
||||
<span {...props} className={classNames("is-flex is-align-items-center has-gap-1", className)} ref={ref}>
|
||||
{typeof children === "function" ? children({ labelId }) : children}
|
||||
</span>
|
||||
);
|
||||
@@ -52,7 +52,7 @@ export const CardDetail = React.forwardRef<HTMLSpanElement, CardDetailProps>(
|
||||
*/
|
||||
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}>
|
||||
<span {...props} className={classNames("has-text-secondary is-size-7", className)} ref={ref}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.44.0
|
||||
@@ -29,3 +32,13 @@
|
||||
const CardRow = "div" as const;
|
||||
|
||||
export default CardRow;
|
||||
|
||||
export const SecondaryRow = React.forwardRef<HTMLDivElement, ComponentProps<typeof CardRow>>(
|
||||
({ className, ...props }, ref) => <CardRow className={classNames(className, "is-size-7")} {...props} ref={ref} />
|
||||
);
|
||||
|
||||
export const TertiaryRow = React.forwardRef<HTMLDivElement, ComponentProps<typeof CardRow>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<CardRow className={classNames(className, "is-size-7", "has-text-secondary")} {...props} ref={ref} />
|
||||
)
|
||||
);
|
||||
|
||||
@@ -24,15 +24,25 @@
|
||||
|
||||
import CardListComponent, { CardListBox as CardListBoxComponent, CardListCard } from "./card-list/CardList";
|
||||
import CardTitle from "./card/CardTitle";
|
||||
import CardRow from "./card/CardRow";
|
||||
import CardRow, { SecondaryRow, TertiaryRow } from "./card/CardRow";
|
||||
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "./card/CardDetail";
|
||||
import CardComponent from "./card/Card";
|
||||
import {
|
||||
DataPageHeader as DataPageHeaderComponent,
|
||||
DataPageHeaderCreateButton,
|
||||
DataPageHeaderSetting,
|
||||
DataPageHeaderSettingField,
|
||||
DataPageHeaderSettingLabel,
|
||||
DataPageHeaderSettings,
|
||||
} from "./templates/data-page/DataPageHeader";
|
||||
|
||||
export { default as Collapsible } from "./collapsible/Collapsible";
|
||||
|
||||
const CardExport = {
|
||||
Title: CardTitle,
|
||||
Row: CardRow,
|
||||
SecondaryRow: SecondaryRow,
|
||||
TertiaryRow: TertiaryRow,
|
||||
Details: Object.assign(CardDetails, {
|
||||
Detail: Object.assign(CardDetail, {
|
||||
Label: CardDetailLabel,
|
||||
@@ -49,3 +59,13 @@ const CardListExport = {
|
||||
|
||||
export const CardList = Object.assign(CardListComponent, CardListExport);
|
||||
export const CardListBox = Object.assign(CardListBoxComponent, CardListExport);
|
||||
|
||||
export const DataPageHeader = Object.assign(DataPageHeaderComponent, {
|
||||
Settings: Object.assign(DataPageHeaderSettings, {
|
||||
Setting: Object.assign(DataPageHeaderSetting, {
|
||||
Label: DataPageHeaderSettingLabel,
|
||||
Field: DataPageHeaderSettingField,
|
||||
}),
|
||||
}),
|
||||
CreateButton: DataPageHeaderCreateButton,
|
||||
});
|
||||
|
||||
200
scm-ui/ui-layout/src/templates/data-page/DataPage.stories.tsx
Normal file
200
scm-ui/ui-layout/src/templates/data-page/DataPage.stories.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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 from "react";
|
||||
import {
|
||||
DataPageHeader,
|
||||
DataPageHeaderCreateButton,
|
||||
DataPageHeaderSetting,
|
||||
DataPageHeaderSettingField,
|
||||
DataPageHeaderSettingLabel,
|
||||
DataPageHeaderSettings,
|
||||
} from "./DataPageHeader";
|
||||
import { Select } from "@scm-manager/ui-forms";
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { ErrorNotification, Loading, Subtitle, Title, Notification } from "@scm-manager/ui-components";
|
||||
import { Button, Icon } from "@scm-manager/ui-buttons";
|
||||
import { CardListBox, CardListCard } from "../../card-list/CardList";
|
||||
import CardRow, { SecondaryRow, TertiaryRow } from "../../card/CardRow";
|
||||
import CardTitle from "../../card/CardTitle";
|
||||
import { Link } from "react-router-dom";
|
||||
import StoryRouter from "storybook-react-router";
|
||||
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "../../card/CardDetail";
|
||||
|
||||
export default {
|
||||
title: "Data Page Template",
|
||||
component: DataPageHeader,
|
||||
decorators: [StoryRouter()],
|
||||
} as ComponentMeta<typeof DataPageHeader>;
|
||||
|
||||
// @ts-ignore Storybook is not cooperating
|
||||
export const Example: ComponentStory<{ error: Error; isLoading: boolean; isEmpty: boolean }> = ({
|
||||
error,
|
||||
isLoading,
|
||||
isEmpty,
|
||||
}: any) => {
|
||||
let content;
|
||||
if (error) {
|
||||
content = <ErrorNotification error={error} />;
|
||||
} else if (isLoading) {
|
||||
content = <Loading />;
|
||||
} else if (isEmpty) {
|
||||
content = <Notification type="info">There is no data, consider adjusting the filters</Notification>;
|
||||
} else {
|
||||
content = (
|
||||
<CardListBox>
|
||||
<CardListCard avatar={<Icon>trash</Icon>} action={<Icon>ellipsis-v</Icon>}>
|
||||
<CardRow>
|
||||
<CardTitle>
|
||||
<Link to="/item">
|
||||
The title may contain a link but most importantly does not contain any information except the "display
|
||||
name" of the entity. It is also text-only
|
||||
</Link>
|
||||
</CardTitle>
|
||||
</CardRow>
|
||||
<SecondaryRow>
|
||||
This contains more important details about the card, but not quite as important as the title.
|
||||
</SecondaryRow>
|
||||
<TertiaryRow>This contains less important information about the card</TertiaryRow>
|
||||
<CardRow>
|
||||
<CardDetails>
|
||||
<CardDetail>
|
||||
<CardDetailLabel>Tags are great for numbers.</CardDetailLabel>
|
||||
<CardDetailTag>7/3</CardDetailTag>
|
||||
</CardDetail>
|
||||
<CardDetail>
|
||||
{({ labelId }) => (
|
||||
<>
|
||||
<CardDetailLabel id={labelId}>
|
||||
Interactive details need 'is-relative' and 'aria-labelledby'
|
||||
</CardDetailLabel>
|
||||
<Button aria-labelledby={labelId} className="is-relative has-background-transparent is-borderless">
|
||||
<Icon>edit</Icon>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</CardDetail>
|
||||
</CardDetails>
|
||||
</CardRow>
|
||||
</CardListCard>
|
||||
<CardListCard avatar={<Icon>users</Icon>} action={<Icon>ellipsis-v</Icon>}>
|
||||
<CardRow>
|
||||
<CardTitle>
|
||||
<Link to="/item">
|
||||
We can also enter insane text without whitespace
|
||||
ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
|
||||
</Link>
|
||||
</CardTitle>
|
||||
</CardRow>
|
||||
<SecondaryRow>
|
||||
SCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCM
|
||||
</SecondaryRow>
|
||||
<TertiaryRow>
|
||||
SCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCM
|
||||
</TertiaryRow>
|
||||
<CardRow>
|
||||
<CardDetails>
|
||||
<CardDetail>
|
||||
<CardDetailLabel>SCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCM</CardDetailLabel>
|
||||
<CardDetailTag>7/3</CardDetailTag>
|
||||
</CardDetail>
|
||||
<CardDetail>
|
||||
{({ labelId }) => (
|
||||
<>
|
||||
<CardDetailLabel id={labelId}>SCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCMSCM</CardDetailLabel>
|
||||
<Button aria-labelledby={labelId} className="is-relative has-background-transparent is-borderless">
|
||||
<Icon>trash</Icon>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</CardDetail>
|
||||
</CardDetails>
|
||||
</CardRow>
|
||||
</CardListCard>
|
||||
</CardListBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title>My Page</Title>
|
||||
<Subtitle subtitle="All the data" />
|
||||
<DataPageHeader>
|
||||
<DataPageHeaderSettings>
|
||||
<DataPageHeaderSetting>
|
||||
{({ formFieldId }) => (
|
||||
<>
|
||||
<DataPageHeaderSettingLabel htmlFor={formFieldId}>Filter by</DataPageHeaderSettingLabel>
|
||||
<DataPageHeaderSettingField>
|
||||
<Select
|
||||
id={formFieldId}
|
||||
options={[
|
||||
{
|
||||
label: "Yes",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: "No",
|
||||
value: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</DataPageHeaderSettingField>
|
||||
</>
|
||||
)}
|
||||
</DataPageHeaderSetting>
|
||||
<DataPageHeaderSetting>
|
||||
{({ formFieldId }) => (
|
||||
<>
|
||||
<DataPageHeaderSettingLabel htmlFor={formFieldId}>Sort by</DataPageHeaderSettingLabel>
|
||||
<DataPageHeaderSettingField>
|
||||
<Select
|
||||
id={formFieldId}
|
||||
options={[
|
||||
{
|
||||
label: "Blue",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: "Red",
|
||||
value: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</DataPageHeaderSettingField>
|
||||
</>
|
||||
)}
|
||||
</DataPageHeaderSetting>
|
||||
</DataPageHeaderSettings>
|
||||
<DataPageHeaderCreateButton to="/mydata/create">Create New Data</DataPageHeaderCreateButton>
|
||||
</DataPageHeader>
|
||||
{content}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Example.args = {
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
isEmpty: false,
|
||||
};
|
||||
100
scm-ui/ui-layout/src/templates/data-page/DataPageHeader.tsx
Normal file
100
scm-ui/ui-layout/src/templates/data-page/DataPageHeader.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 withClasses from "../../_helpers/with-classes";
|
||||
import React, { HTMLAttributes } from "react";
|
||||
import classNames from "classnames";
|
||||
import { useGeneratedId } from "@scm-manager/ui-components";
|
||||
import { LinkButton } from "@scm-manager/ui-buttons";
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.47.0
|
||||
*/
|
||||
export const DataPageHeader = withClasses("div", [
|
||||
"is-flex",
|
||||
"is-flex-wrap-wrap",
|
||||
"is-justify-content-space-between",
|
||||
"mb-3",
|
||||
"has-row-gap-2",
|
||||
"has-column-gap-4",
|
||||
]);
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.47.0
|
||||
*/
|
||||
export const DataPageHeaderSettings = withClasses("div", [
|
||||
"is-flex",
|
||||
"is-flex-wrap-wrap",
|
||||
"is-align-items-center",
|
||||
"has-row-gap-2",
|
||||
"has-column-gap-4",
|
||||
"is-flex-grow-1",
|
||||
"is-flex-shrink-1",
|
||||
"is-flex-basis-0",
|
||||
]);
|
||||
|
||||
type DataPageHeaderSettingProps = HTMLAttributes<HTMLDivElement> & {
|
||||
children?: React.ReactNode | ((props: { formFieldId: string }) => React.ReactNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.47.0
|
||||
*/
|
||||
export const DataPageHeaderSetting = React.forwardRef<HTMLSpanElement, DataPageHeaderSettingProps>(
|
||||
({ className, children, ...props }, ref) => {
|
||||
const formFieldId = useGeneratedId();
|
||||
return (
|
||||
<span {...props} className={classNames(className, "is-flex", "is-align-items-center", "has-gap-2")} ref={ref}>
|
||||
{typeof children === "function" ? children({ formFieldId }) : children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.47.0
|
||||
*/
|
||||
export const DataPageHeaderSettingLabel = withClasses("label", [
|
||||
"is-flex",
|
||||
"is-align-items-center",
|
||||
"is-text-wrap-no-wrap",
|
||||
]);
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.47.0
|
||||
*/
|
||||
export const DataPageHeaderSettingField = React.Fragment;
|
||||
|
||||
/**
|
||||
* @beta
|
||||
* @since 2.47.0
|
||||
*/
|
||||
export const DataPageHeaderCreateButton = withClasses(LinkButton, ["is-flex-grow-0", "is-flex-shrink-0"], {
|
||||
variant: "primary",
|
||||
});
|
||||
29
scm-ui/ui-styles/src/components/_flex.scss
Normal file
29
scm-ui/ui-styles/src/components/_flex.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@each $size, $value in $spacing-values {
|
||||
.is-flex-basis-#{$size} {
|
||||
flex-basis: $value;
|
||||
}
|
||||
}
|
||||
35
scm-ui/ui-styles/src/components/_gap.scss
Normal file
35
scm-ui/ui-styles/src/components/_gap.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@each $size, $value in $spacing-values {
|
||||
.has-gap-#{$size} {
|
||||
gap: $value;
|
||||
}
|
||||
.has-row-gap-#{$size} {
|
||||
row-gap: $value;
|
||||
}
|
||||
.has-column-gap-#{$size} {
|
||||
column-gap: $value;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.is-overflow-wrap-anywhere {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.is-text-wrap-no-wrap {
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.is-absolute {
|
||||
position: absolute;
|
||||
|
||||
@@ -25,5 +25,7 @@
|
||||
@import "../variables/_derived.scss";
|
||||
@import "bulma-popover/css/bulma-popover";
|
||||
@import "../components/_main.scss";
|
||||
@import "../components/_gap.scss";
|
||||
@import "../components/_flex.scss";
|
||||
@import "../components/_tooltip.scss";
|
||||
@import "../components/_card.scss";
|
||||
|
||||
@@ -30,19 +30,9 @@ import { useTranslation } from "react-i18next";
|
||||
import { useBranchDetailsCollection } from "@scm-manager/ui-api";
|
||||
import { KeyboardIterator } from "@scm-manager/ui-shortcuts";
|
||||
import BranchList from "../components/BranchList";
|
||||
import { Collapsible } from "@scm-manager/ui-layout";
|
||||
import { LinkButton } from "@scm-manager/ui-buttons";
|
||||
import { Collapsible, DataPageHeader } from "@scm-manager/ui-layout";
|
||||
import { Select } from "@scm-manager/ui-forms";
|
||||
import { SORT_OPTIONS, SortOption } from "../../tags/orderTags";
|
||||
import styled from "styled-components";
|
||||
|
||||
const BranchListWrapper = styled.div`
|
||||
gap: 1rem;
|
||||
`;
|
||||
|
||||
const HeaderWrapper = styled.div`
|
||||
gap: 0.5rem 1rem;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
@@ -74,26 +64,32 @@ const BranchTableWrapper: FC<Props> = ({ repository, baseUrl, data }) => {
|
||||
<>
|
||||
<Subtitle subtitle={t("branches.overview.title")} />
|
||||
<ErrorNotification error={error} />
|
||||
<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>
|
||||
<DataPageHeader>
|
||||
<DataPageHeader.Settings className="is-flex is-align-items-center">
|
||||
<DataPageHeader.Settings.Setting>
|
||||
{({ formFieldId }) => (
|
||||
<>
|
||||
<DataPageHeader.Settings.Setting.Label htmlFor={formFieldId}>
|
||||
{t("branches.overview.sort.label")}
|
||||
</DataPageHeader.Settings.Setting.Label>
|
||||
<DataPageHeader.Settings.Setting.Field>
|
||||
<Select id={formFieldId} 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>
|
||||
</DataPageHeader.Settings.Setting.Field>
|
||||
</>
|
||||
)}
|
||||
</DataPageHeader.Settings.Setting>
|
||||
</DataPageHeader.Settings>
|
||||
{showCreateButton ? (
|
||||
<LinkButton variant="primary" to="./create">
|
||||
{t("branches.overview.createButton")}
|
||||
</LinkButton>
|
||||
<DataPageHeader.CreateButton to="./create">{t("branches.overview.createButton")}</DataPageHeader.CreateButton>
|
||||
) : null}
|
||||
</HeaderWrapper>
|
||||
<BranchListWrapper className="is-flex is-flex-direction-column">
|
||||
</DataPageHeader>
|
||||
<div className="is-flex is-flex-direction-column has-gap-4">
|
||||
<KeyboardIterator>
|
||||
{activeBranches.length > 0 ? (
|
||||
<Collapsible header={t("branches.table.branches.active")}>
|
||||
@@ -116,7 +112,7 @@ const BranchTableWrapper: FC<Props> = ({ repository, baseUrl, data }) => {
|
||||
</Collapsible>
|
||||
) : null}
|
||||
</KeyboardIterator>
|
||||
</BranchListWrapper>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user