Add new api for interactive card details

Committed-by: Rene Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2023-09-21 11:26:41 +02:00
parent eabf5ebafd
commit 0c055ca6df
19 changed files with 337 additions and 172 deletions

View File

@@ -0,0 +1,2 @@
- type: added
description: New api for interactive card details

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
import React from "react";
import React, { ComponentProps } from "react";
import {
Button as ButtonComponent,
@@ -34,8 +34,6 @@ import {
import StoryRouter from "storybook-react-router";
import { StoryFn } from "@storybook/react";
type ExtractProps<T> = T extends React.ComponentType<infer U> ? U : never;
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: "Components",
@@ -58,11 +56,11 @@ export default {
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const ButtonTemplate: StoryFn<ExtractProps<typeof ButtonComponent>> = (args) => <ButtonComponent {...args} />;
const LinkButtonTemplate: StoryFn<ExtractProps<typeof LinkButtonComponent>> = (args) => (
const ButtonTemplate: StoryFn<ComponentProps<typeof ButtonComponent>> = (args) => <ButtonComponent {...args} />;
const LinkButtonTemplate: StoryFn<ComponentProps<typeof LinkButtonComponent>> = (args) => (
<LinkButtonComponent {...args} />
);
const ExternalLinkButtonTemplate: StoryFn<ExtractProps<typeof ExternalLinkButtonComponent>> = (args) => (
const ExternalLinkButtonTemplate: StoryFn<ComponentProps<typeof ExternalLinkButtonComponent>> = (args) => (
<ExternalLinkButtonComponent {...args} />
);

View File

@@ -71110,7 +71110,7 @@ exports[`Storyshots Repositories/RepositoryEntry Archived 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71118,7 +71118,7 @@ exports[`Storyshots Repositories/RepositoryEntry Archived 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -71132,12 +71132,12 @@ exports[`Storyshots Repositories/RepositoryEntry Archived 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71238,7 +71238,7 @@ exports[`Storyshots Repositories/RepositoryEntry Avatar EP 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71246,7 +71246,7 @@ exports[`Storyshots Repositories/RepositoryEntry Avatar EP 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -71260,12 +71260,12 @@ exports[`Storyshots Repositories/RepositoryEntry Avatar EP 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71354,7 +71354,7 @@ exports[`Storyshots Repositories/RepositoryEntry Before Title EP 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71362,7 +71362,7 @@ exports[`Storyshots Repositories/RepositoryEntry Before Title EP 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<i
className="far fa-star"
@@ -71379,12 +71379,12 @@ exports[`Storyshots Repositories/RepositoryEntry Before Title EP 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71473,7 +71473,7 @@ exports[`Storyshots Repositories/RepositoryEntry Default 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71481,7 +71481,7 @@ exports[`Storyshots Repositories/RepositoryEntry Default 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -71495,12 +71495,12 @@ exports[`Storyshots Repositories/RepositoryEntry Default 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71589,7 +71589,7 @@ exports[`Storyshots Repositories/RepositoryEntry Exporting 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71597,7 +71597,7 @@ exports[`Storyshots Repositories/RepositoryEntry Exporting 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -71611,12 +71611,12 @@ exports[`Storyshots Repositories/RepositoryEntry Exporting 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71717,7 +71717,7 @@ exports[`Storyshots Repositories/RepositoryEntry HealthCheck Failure 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71725,7 +71725,7 @@ exports[`Storyshots Repositories/RepositoryEntry HealthCheck Failure 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -71739,12 +71739,12 @@ exports[`Storyshots Repositories/RepositoryEntry HealthCheck Failure 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71846,7 +71846,7 @@ exports[`Storyshots Repositories/RepositoryEntry MultiRepositoryTags 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71854,7 +71854,7 @@ exports[`Storyshots Repositories/RepositoryEntry MultiRepositoryTags 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -71868,12 +71868,12 @@ exports[`Storyshots Repositories/RepositoryEntry MultiRepositoryTags 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -71986,7 +71986,7 @@ exports[`Storyshots Repositories/RepositoryEntry RepositoryFlag EP 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71994,7 +71994,7 @@ exports[`Storyshots Repositories/RepositoryEntry RepositoryFlag EP 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -72008,12 +72008,12 @@ exports[`Storyshots Repositories/RepositoryEntry RepositoryFlag EP 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"
@@ -72115,7 +72115,7 @@ exports[`Storyshots Repositories/RepositoryEntry With long texts 1`] = `
</div>
</div>
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -72123,7 +72123,7 @@ exports[`Storyshots Repositories/RepositoryEntry With long texts 1`] = `
}
>
<div
className="is-flex"
className="is-flex ml-1"
>
<h4>
<a
@@ -72137,12 +72137,12 @@ exports[`Storyshots Repositories/RepositoryEntry With long texts 1`] = `
</h4>
</div>
<div
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7"
className="RepositoryEntry__DescriptionRow-sc-6jys82-2 dzxasi is-size-7 ml-1"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
</div>
<div
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap"
className="RepositoryEntry__DetailsRow-sc-6jys82-3 kXjiC is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap ml-1"
>
<span
className="is-size-7 has-text-secondary is-relative"

View File

@@ -21,9 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { extensionPoints, ExtractProps } from "@scm-manager/ui-extensions";
import { extensionPoints } from "@scm-manager/ui-extensions";
import { ComponentProps } from "react";
export type ProtocolLinkRendererProps<Protocol extends string | undefined = undefined> = ExtractProps<
export type ProtocolLinkRendererProps<Protocol extends string | undefined = undefined> = ComponentProps<
extensionPoints.MarkdownLinkProtocolRenderer<Protocol>["type"]["renderer"]
>;

View File

@@ -22,7 +22,7 @@
* SOFTWARE.
*/
import React, { ComponentType, FC, ReactNode } from "react";
import React, { ComponentProps, ComponentType, FC, ReactNode } from "react";
import {
Branch,
BranchDetails,
@@ -49,8 +49,6 @@ import {
} from "@scm-manager/ui-types";
import { ExtensionPointDefinition } from "./binder";
import { RenderableExtensionPointDefinition, SimpleRenderableDynamicExtensionPointDefinition } from "./ExtensionPoint";
import ExtractProps from "./extractProps";
import { useGeneratedId } from "@scm-manager/ui-components";
type RepositoryCreatorSubFormProps = {
repository: RepositoryCreation;
@@ -59,7 +57,7 @@ type RepositoryCreatorSubFormProps = {
disabled?: boolean;
};
export type RepositoryCreatorComponentProps = ExtractProps<RepositoryCreator["type"]["component"]>;
export type RepositoryCreatorComponentProps = ComponentProps<RepositoryCreator["type"]["component"]>;
/**
* @deprecated use {@link RepositoryCreator}`["type"]` instead

View File

@@ -21,8 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React from "react";
import { ComponentProps, JSXElementConstructor } from "react";
type ExtractProps<T> = T extends React.ComponentType<infer U> ? U : never;
/**
* @deprecated Use {@link ComponentProps} directly instead
*/
type ExtractProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> = ComponentProps<T>;
export default ExtractProps;

View File

@@ -37,6 +37,7 @@
"classnames": "2",
"react": "17",
"react-dom": "17",
"react-router-dom": "5",
"styled-components": "5",
"@scm-manager/ui-components": "2.46.2-SNAPSHOT"
},

View File

@@ -23,16 +23,15 @@
*/
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 { ComponentMeta, ComponentStory } from "@storybook/react";
import React from "react";
import { Link } from "react-router-dom";
import CardList, { CardListBox, CardListCard } from "./CardList";
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";
import CardRow, { SecondaryRow } from "../card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag, CardLinkDetail } from "../card/CardDetail";
export default {
title: "CardList",
@@ -40,15 +39,8 @@ export default {
decorators: [StoryRouter()],
} as ComponentMeta<typeof CardList>;
const Template: StoryFn<ExtractProps<typeof CardListBox>> = (args) => <CardListBox {...args} />;
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {
children: [
<CardListCard>
<CardTitle>My favorite repository</CardTitle>
</CardListCard>,
export const Default: ComponentStory<typeof CardListBox> = () => (
<CardListBox>
<CardListCard
action={
<Menu>
@@ -59,13 +51,20 @@ Default.args = {
</Menu>
}
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur
sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata
sanctus est Lorem ipsum dolor sit amet.
</CardListCard>,
<CardRow>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
takimata sanctus est Lorem ipsum dolor sit amet.
</CardRow>
</CardListCard>
<CardListCard>
<CardRow>
<CardTitle>My favorite repository</CardTitle>
</CardRow>
</CardListCard>
<CardListCard
action={
<Menu>
@@ -76,19 +75,23 @@ Default.args = {
</Menu>
}
>
<CardTitle level={5}>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My favorite repository with a very long title and some other things
</Link>
</CardTitle>
</CardListCard>,
<CardRow>
<CardTitle level={5}>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My favorite repository with a very long title and some other things
</Link>
</CardTitle>
</CardRow>
</CardListCard>
<CardListCard>
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My least liked repo
</Link>
</CardTitle>
</CardListCard>,
<CardRow>
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My least liked repo
</Link>
</CardTitle>
</CardRow>
</CardListCard>
<CardListCard
action={
<Menu>
@@ -114,7 +117,7 @@ Default.args = {
<CardRow className="is-size-6 is-flex is-justify-content-space-between">
<span>This is a third row, lets see how this works out.</span>(MERGED)
</CardRow>
</CardListCard>,
</CardListCard>
<CardListCard>
<CardRow className="is-flex is-align-items-center">
<CardTitle>
@@ -140,31 +143,59 @@ Default.args = {
</div>
<span>(OPEN)</span>
</CardRow>
</CardListCard>,
</CardListCard>
<CardListCard>
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My least liked repo
</Link>
</CardTitle>
<CardRow>
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
My least liked repo
</Link>
</CardTitle>
</CardRow>
<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>
<CardDetailLabel>Workers</CardDetailLabel>
<CardDetailTag>2/3</CardDetailTag>
</CardDetail>
<CardLinkDetail to="/my/detail">
<CardDetailLabel>Go to docs</CardDetailLabel>
</CardLinkDetail>
</CardDetails>
</CardRow>
</CardListCard>,
],
} as ComponentProps<typeof CardListBox>;
</CardListCard>
<CardListCard avatar={<Icon>user</Icon>} action={<Icon>ellipsis-v</Icon>}>
<CardRow>
<CardTitle>
3c991eec687444630c0929e4e23d8a1a2565011d4bea28d4338dd1d024bb74c8
c076ca5eea66545ee227c8854acc8b9bf075676a6848d54fa0bc1fa291f78887
72d7e2ef64c9575dd8ceeed8ed6b24f185646deb6595b13fd51c5705c61a2c46
9127daa67e066bc49f39cca9670b92de3d576ac1fb9c916e9b44692923e12a9d
14d07434e8c5b3d0ba2e752bc580888a30963d4e8021be573392bb625f6150da
60fc6f2e7503b1ca5963afb627ef560f4e2191c0da4c9328ae4ab088e177fb41
749e63a6af1731d5c599e960a2f6c8cb9a15d6cf6a82493f419d417829f7b2a8
0ca9334aeda2e5dab101e4af13c9610839afc3b9dd2ec56ffb067d6914ce9b67
b708983948a1750f79fbb91875399fcce453410dad6191c5dc5059f4b28aee1d
0d13a4349270947bc79cfc59c7c2aa59960d847a49b40feccd3388fa9a600a68
</CardTitle>
</CardRow>
<SecondaryRow className="is-ellipsis-overflow">
7cab5486ab8cd946af71a77d37c84bac c05156ef54a1f0bfd5d4fa12f774148c 4f2964e1895470b6313e3264fef276d8
8c57fdf7fde5fc227ea0c59a0f359122 3bc64067ff6fb9c64f4ae5ac15e4375d 91943ad0c020859ad6cc3723fe9bd325
07bb6d93d9faf2df68d02949ec10e58e 0bee2b579b7ab5777683f3d5b5975960 4a3009269d971f555524374e7da745ad
a693ffb57da89f191249de6480c2387b
</SecondaryRow>
<CardRow>
<CardDetails>
<CardDetail>
<CardDetailLabel>Workers</CardDetailLabel>
<CardDetailTag>2/3</CardDetailTag>
</CardDetail>
<CardLinkDetail to="/to/docs">
<CardDetailLabel>Go to docs</CardDetailLabel>
</CardLinkDetail>
</CardDetails>
</CardRow>
</CardListCard>
</CardListBox>
);

View File

@@ -23,14 +23,23 @@
*/
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 { ComponentMeta, ComponentStory } from "@storybook/react";
import React from "react";
import { Link } from "react-router-dom";
import CardTitle from "../card/CardTitle";
import CardRow from "../card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "./CardDetail";
import CardRow, { SecondaryRow, TertiaryRow } from "../card/CardRow";
import {
CardButtonDetail,
CardDetail,
CardDetailIcon,
CardDetailLabel,
CardDetails,
CardDetailTag,
CardLinkDetail,
} from "./CardDetail";
import Card from "./Card";
import { Popover } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
export default {
title: "Card",
@@ -38,35 +47,67 @@ export default {
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>,
export const Default: ComponentStory<typeof Card> = () => (
<Card
className="box"
avatar={<Icon className="is-large">user</Icon>}
action={<Icon className="is-medium">ellipsis-v</Icon>}
>
<CardRow>
<CardTitle>
<Link aria-label="Edit My least liked repo" to="/cards/1">
3c991eec687444630c0929e4e23d8a1a2565011d4bea28d4338dd1d024bb74c8
c076ca5eea66545ee227c8854acc8b9bf075676a6848d54fa0bc1fa291f78887
72d7e2ef64c9575dd8ceeed8ed6b24f185646deb6595b13fd51c5705c61a2c46
9127daa67e066bc49f39cca9670b92de3d576ac1fb9c916e9b44692923e12a9d
14d07434e8c5b3d0ba2e752bc580888a30963d4e8021be573392bb625f6150da
60fc6f2e7503b1ca5963afb627ef560f4e2191c0da4c9328ae4ab088e177fb41
749e63a6af1731d5c599e960a2f6c8cb9a15d6cf6a82493f419d417829f7b2a8
0ca9334aeda2e5dab101e4af13c9610839afc3b9dd2ec56ffb067d6914ce9b67
b708983948a1750f79fbb91875399fcce453410dad6191c5dc5059f4b28aee1d
0d13a4349270947bc79cfc59c7c2aa59960d847a49b40feccd3388fa9a600a68
</Link>
</CardTitle>
</CardRow>
<SecondaryRow className="is-ellipsis-overflow">
7cab5486ab8cd946af71a77d37c84bac c05156ef54a1f0bfd5d4fa12f774148c 4f2964e1895470b6313e3264fef276d8
8c57fdf7fde5fc227ea0c59a0f359122 3bc64067ff6fb9c64f4ae5ac15e4375d 91943ad0c020859ad6cc3723fe9bd325
07bb6d93d9faf2df68d02949ec10e58e 0bee2b579b7ab5777683f3d5b5975960 4a3009269d971f555524374e7da745ad
a693ffb57da89f191249de6480c2387b
</SecondaryRow>
<TertiaryRow>This is information is not important</TertiaryRow>
<CardRow>
<CardDetails>
<Popover
trigger={
<CardButtonDetail>
<CardDetailLabel>Popover Detail with Tag</CardDetailLabel>
<CardDetailTag>3</CardDetailTag>
</CardButtonDetail>
}
title="My Popover Details"
>
<>This is some additional detail for my Popover.</>
</Popover>
<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>
<CardDetailLabel>Normal Detail with Tag</CardDetailLabel>
<CardDetailTag>2/3</CardDetailTag>
</CardDetail>
<CardLinkDetail to="/workers">
<CardDetailLabel>Link Detail</CardDetailLabel>
</CardLinkDetail>
<Popover
trigger={
<CardButtonDetail>
<CardDetailLabel>Popover Detail with Icon</CardDetailLabel>
<CardDetailIcon className="has-text-success">check-circle</CardDetailIcon>
</CardButtonDetail>
}
title="My Popover Details"
>
<>This is some additional detail for my Popover.</>
</Popover>
</CardDetails>
</CardRow>,
],
} as ComponentProps<typeof Card>;
</CardRow>
</Card>
);

View File

@@ -25,6 +25,11 @@
import React, { ComponentType, HTMLAttributes, ReactHTML, Ref } from "react";
import classNames from "classnames";
import CSS from "csstype";
import styled from "styled-components";
const RowsContainer = styled.div`
margin-left: -0.25rem;
`;
type Props = HTMLAttributes<HTMLElement> & {
action?: React.ReactElement;
@@ -49,7 +54,7 @@ type Props = HTMLAttributes<HTMLElement> & {
* @since 2.44.0
*/
const Card = React.forwardRef<HTMLElement, Props>(
({ className, avatar, rowGap = "0.5rem", children, as: Comp = "div", action, ...props }, ref) =>
({ className, avatar, rowGap = "0.25rem", children, as: Comp = "div", action, ...props }, ref) =>
React.createElement(
Comp,
{
@@ -58,12 +63,12 @@ const Card = React.forwardRef<HTMLElement, Props>(
...props,
},
avatar ? avatar : null,
<div
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-wrap-anywhere"
<RowsContainer
className="is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={{ gap: rowGap }}
>
{children}
</div>,
</RowsContainer>,
action ? action : null
)
);

View File

@@ -22,10 +22,18 @@
* SOFTWARE.
*/
import React, { HTMLAttributes, ReactNode } from "react";
import React, {
ButtonHTMLAttributes,
ComponentProps,
ComponentPropsWithoutRef,
HTMLAttributes,
ReactNode,
} from "react";
import classNames from "classnames";
import { useGeneratedId } from "@scm-manager/ui-components";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { Icon } from "@scm-manager/ui-buttons";
export const CardVariants = {
LIGHT: "light",
@@ -56,13 +64,73 @@ 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 has-gap-1", className)} ref={ref}>
<span {...props} className={classNames("is-flex is-align-items-center has-gap-1 p-1", className)} ref={ref}>
{typeof children === "function" ? children({ labelId }) : children}
</span>
);
}
);
const InteractiveDetailStyles = `
&:focus {
outline-offset: -0.125rem;
outline-width: 0.125rem;
}
`;
const StyledCardButtonDetail = styled.button`
${InteractiveDetailStyles}
&[aria-expanded="true"] {
outline: var(--scm-border-color) solid 0.125rem;
outline-offset: -0.125rem;
}
`;
/**
* @beta
* @since 2.47.0
*/
export const CardButtonDetail = React.forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement>>(
({ children, className, ...props }, ref) => (
<StyledCardButtonDetail
{...props}
className={classNames(
"is-flex is-align-items-center has-gap-1 p-1 is-borderless has-background-transparent is-relative has-hover-background-blue has-rounded-border is-clickable",
className
)}
ref={ref}
>
{children}
</StyledCardButtonDetail>
)
);
const StyledCardLinkDetail = styled(Link)`
${InteractiveDetailStyles}
min-height: 2rem;
`;
/**
* @beta
* @since 2.47.0
*/
export const CardLinkDetail = React.forwardRef<HTMLAnchorElement, ComponentPropsWithoutRef<typeof Link>>(
({ children, className, ...props }, ref) => (
<StyledCardLinkDetail
{...props}
className={classNames(
"is-flex is-align-items-center has-gap-1 p-1 is-borderless has-background-transparent is-relative has-hover-background-blue has-rounded-border is-clickable",
className
)}
ref={ref}
>
{children}
</StyledCardLinkDetail>
)
);
/**
* @beta
* @since 2.46.0
@@ -91,8 +159,24 @@ export const CardDetailTag = React.forwardRef<HTMLSpanElement, HTMLAttributes<HT
)
);
/**
* @beta
* @since 2.47.0
*/
export const CardDetailIcon = React.forwardRef<HTMLElement, ComponentProps<typeof Icon>>(
({ children, className, ...props }, ref) => (
<Icon {...props} className={classNames("is-size-5", className)} ref={ref}>
{children}
</Icon>
)
);
const CardDetailsRowContainer = styled.div`
gap: 0.5rem 1rem;
gap: 0.125rem 1rem;
&:first-child {
margin-left: -0.25rem;
}
`;
/**

View File

@@ -22,14 +22,16 @@
* SOFTWARE.
*/
import React, { ComponentProps } from "react";
import React, { ComponentProps, HTMLAttributes } from "react";
import classNames from "classnames";
/**
* @beta
* @since 2.44.0
*/
const CardRow = "div" as const;
const CardRow = React.forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div className={classNames(className, "ml-1")} {...props} ref={ref} />
));
export default CardRow;

View File

@@ -25,11 +25,6 @@
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 { Menu } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
import CardRow from "../card/CardRow";
import Collapsible from "./Collapsible";
export default {
@@ -38,7 +33,7 @@ export default {
decorators: [StoryRouter()],
} as ComponentMeta<typeof Collapsible>;
const Template: StoryFn<ExtractProps<typeof Collapsible>> = (args) => <Collapsible {...args} />;
const Template: StoryFn<ComponentProps<typeof Collapsible>> = (args) => <Collapsible {...args} />;
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args

View File

@@ -25,7 +25,14 @@
import CardListComponent, { CardListBox as CardListBoxComponent, CardListCard } from "./card-list/CardList";
import CardTitle from "./card/CardTitle";
import CardRow, { SecondaryRow, TertiaryRow } from "./card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag } from "./card/CardDetail";
import {
CardButtonDetail,
CardDetail,
CardDetailLabel,
CardDetails,
CardDetailTag,
CardLinkDetail,
} from "./card/CardDetail";
import CardComponent from "./card/Card";
import {
DataPageHeader as DataPageHeaderComponent,
@@ -38,16 +45,20 @@ import {
export { default as Collapsible } from "./collapsible/Collapsible";
const CardDetailExports = {
Label: CardDetailLabel,
Tag: CardDetailTag,
};
const CardExport = {
Title: CardTitle,
Row: CardRow,
SecondaryRow: SecondaryRow,
TertiaryRow: TertiaryRow,
Details: Object.assign(CardDetails, {
Detail: Object.assign(CardDetail, {
Label: CardDetailLabel,
Tag: CardDetailTag,
}),
Detail: Object.assign(CardDetail, CardDetailExports),
ButtonDetail: Object.assign(CardButtonDetail, CardDetailExports),
LinkDetail: Object.assign(CardLinkDetail, CardDetailExports),
}),
};

View File

@@ -25,7 +25,6 @@
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 { Button } from "@scm-manager/ui-buttons";
import Dialog from "./Dialog";
@@ -36,7 +35,7 @@ export default {
render: (story) => <div style={{ height: "100vh" }}>{story}</div>,
} as ComponentMeta<typeof Dialog>;
const Template: StoryFn<ExtractProps<typeof Dialog>> = (args) => <Dialog {...args} />;
const Template: StoryFn<ComponentProps<typeof Dialog>> = (args) => <Dialog {...args} />;
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args

View File

@@ -25,7 +25,6 @@
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 Menu, { MenuButton, MenuDialog, MenuExternalLink, MenuLink } from "./Menu";
import { Button, Icon } from "@scm-manager/ui-buttons";
import { CloseButton } from "../dialog/Dialog";
@@ -36,7 +35,7 @@ export default {
decorators: [StoryRouter()],
} as ComponentMeta<typeof Menu>;
const Template: StoryFn<ExtractProps<typeof Menu>> = (args) => <Menu {...args} />;
const Template: StoryFn<ComponentProps<typeof Menu>> = (args) => <Menu {...args} />;
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args

View File

@@ -22,6 +22,7 @@
* SOFTWARE.
*/
import React, {
ComponentProps,
Dispatch,
FC,
KeyboardEvent as ReactKeyboardEvent,
@@ -66,10 +67,9 @@ const namespaceAndName = (hit: Hit) => {
const name = (hit.fields["name"] as ValueHitField).value as string;
return `${namespace}/${name}`;
};
type ExtractProps<T> = T extends React.ComponentType<infer U> ? U : never;
type HitsProps = {
entries: ReactElement<ExtractProps<typeof HitEntry>>[];
entries: ReactElement<ComponentProps<typeof HitEntry>>[];
hits: Hit[];
showHelp: () => void;
ariaId: string;

View File

@@ -33,7 +33,6 @@ import { SmallLoadingSpinner } from "@scm-manager/ui-components";
type Props = {
branch: Branch;
details?: BranchDetails;
labelId?: string;
};
const Count = styled.span`
@@ -41,7 +40,7 @@ const Count = styled.span`
width: max-content;
`;
const AheadBehindTag: FC<Props> = ({ branch, details, labelId }) => {
const AheadBehindTag: FC<Props> = ({ branch, details }) => {
const [t] = useTranslation("repos");
if (branch.defaultBranch) {
@@ -56,7 +55,7 @@ const AheadBehindTag: FC<Props> = ({ branch, details, labelId }) => {
<Tooltip
message={t("branch.aheadBehind.tooltip", { ahead: details.changesetsAhead, behind: details.changesetsBehind })}
>
<span aria-labelledby={labelId} className="is-inline-flex is-align-items-center">
<span 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}

View File

@@ -122,12 +122,8 @@ const BranchListItem: FC<Props> = ({ branch, remove, isLoading, branchesDetails,
<Card.Details>
{!branch.defaultBranch ? (
<Card.Details.Detail>
{({ labelId }) => (
<>
<Card.Details.Detail.Label id={labelId}>{t("branch.aheadBehind.label")}</Card.Details.Detail.Label>
<AheadBehindTag branch={branch} details={branchDetails} labelId={labelId} />
</>
)}
<Card.Details.Detail.Label>{t("branch.aheadBehind.label")}</Card.Details.Detail.Label>
<AheadBehindTag branch={branch} details={branchDetails} />
</Card.Details.Detail>
) : null}
<ExtensionPoint<extensionPoints.BranchListDetail>