diff --git a/gradle/changelog/added_iconbutton.yaml b/gradle/changelog/added_iconbutton.yaml
new file mode 100644
index 0000000000..07fb9647ca
--- /dev/null
+++ b/gradle/changelog/added_iconbutton.yaml
@@ -0,0 +1,2 @@
+- type: added
+ description: New component "icon button" in ui library
diff --git a/scm-ui/ui-core/src/base/buttons/Button.stories.tsx b/scm-ui/ui-core/src/base/buttons/Button.stories.tsx
index 48bc1ea507..a63cb45882 100644
--- a/scm-ui/ui-core/src/base/buttons/Button.stories.tsx
+++ b/scm-ui/ui-core/src/base/buttons/Button.stories.tsx
@@ -30,7 +30,9 @@ import {
ButtonVariants,
ExternalLinkButton as ExternalLinkButtonComponent,
LinkButton as LinkButtonComponent,
+ IconButton as IconButtonComponent,
} from "./Button";
+import Icon from "./Icon";
import StoryRouter from "storybook-react-router";
import { StoryFn } from "@storybook/react";
@@ -42,6 +44,7 @@ export default {
Button: ButtonComponent,
LinkButton: LinkButtonComponent,
ExternalLinkButton: ExternalLinkButtonComponent,
+ IconButton: IconButtonComponent,
},
argTypes: {
variant: {
@@ -64,6 +67,10 @@ const ExternalLinkButtonTemplate: StoryFn
);
+const IconButtonTemplate: StoryFn> = (args) => (
+
+);
+
export const Button = ButtonTemplate.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Button.args = {
@@ -87,3 +94,41 @@ ExternalLinkButton.args = {
href: "https://scm-manager.org",
variant: ButtonVariants.PRIMARY,
};
+
+const smallIcon = trash;
+const mediumIcon = trash;
+
+ /*
+
+ Variant and size are defaulted to medium and colored and do not have to be explicitly added as parameters.
+ However for the sake of documentation here they are still passed in
+
+ */
+export const IconButtonBorder = IconButtonTemplate.bind({});
+IconButtonBorder.args = {
+ children: mediumIcon,
+ variant: "colored",
+ size: "medium",
+};
+
+
+export const IconButtonBorderDefault = IconButtonTemplate.bind({});
+IconButtonBorderDefault.args = {
+ children: mediumIcon,
+ variant: "default",
+ size: "medium",
+};
+
+export const IconButtonBorderlessSmall = IconButtonTemplate.bind({});
+IconButtonBorderlessSmall.args = {
+ children: smallIcon,
+ variant: "colored",
+ size: "small",
+};
+
+export const IconButtonBorderlessSmallDefault = IconButtonTemplate.bind({});
+IconButtonBorderlessSmallDefault.args = {
+ children: smallIcon,
+ variant: "default",
+ size: "small",
+};
diff --git a/scm-ui/ui-core/src/base/buttons/Button.tsx b/scm-ui/ui-core/src/base/buttons/Button.tsx
index b66883f405..4bae4f7109 100644
--- a/scm-ui/ui-core/src/base/buttons/Button.tsx
+++ b/scm-ui/ui-core/src/base/buttons/Button.tsx
@@ -22,10 +22,11 @@
* SOFTWARE.
*/
-import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from "react";
+import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from "react";
import { Link as ReactRouterLink, LinkProps as ReactRouterLinkProps } from "react-router-dom";
import classNames from "classnames";
import { createAttributesForTesting } from "../helpers";
+import styled from "styled-components";
/**
* @beta
@@ -141,3 +142,85 @@ export const ExternalLinkButton = React.forwardRef
)
);
+
+const StyledIconButton = styled.button`
+ border-radius: 6px !important;
+ min-width: 2.5rem;
+ height: 2.5rem;
+ padding: 0.5em;
+ &:not(:disabled) {
+ &:hover {
+ background-color: color-mix(in srgb, currentColor 10%, transparent);
+ }
+ &:active {
+ background-color: color-mix(in srgb, currentColor 25%, transparent);
+ }
+ }
+`;
+
+const StyledIconButtonCircle = styled.button`
+ border-radius: 50% !important;
+ border: None;
+ min-width: 1.5rem;
+ height: 1.5rem;
+ padding: 0.5em;
+ &:not(:disabled) {
+ &:hover {
+ background-color: color-mix(in srgb, currentColor 10%, transparent);
+ }
+ &:active {
+ background-color: color-mix(in srgb, currentColor 25%, transparent);
+ }
+ }
+`;
+
+export const IconButtonVariants = {
+ COLORED: "colored",
+ DEFAULT: "default",
+} as const;
+
+export const IconSizes = {
+ SMALL: "small",
+ MEDIUM: "medium",
+} as const;
+
+type IconButtonSize = typeof IconSizes[keyof typeof IconSizes];
+
+type IconButtonVariant = typeof IconButtonVariants[keyof typeof IconButtonVariants];
+
+const createIconButtonClasses = (variant?: IconButtonVariant) =>
+ classNames("button", {
+ "is-primary is-outlined": variant === "colored",
+ });
+
+type IconButtonProps = ButtonHTMLAttributes & {
+ testId?: string;
+ variant?: IconButtonVariant;
+ children: ReactNode;
+ size?: IconButtonSize;
+};
+
+/**
+ * Styled html button.
+ *
+ * An icon button has to declare an `aria-label`.
+ *
+ * @beta
+ * @since 3.2.0
+ */
+export const IconButton = React.forwardRef(
+ ({ className, variant = "default", size = "medium", testId, type, children, ...props }, ref) => {
+ const elementAttributes = {
+ type: type ?? "button",
+ ...props,
+ className: classNames(createIconButtonClasses(variant), "button", "has-background-transparent"),
+ ref: ref,
+ ...createAttributesForTesting(testId),
+ };
+ return size === "small" ? (
+ {children}
+ ) : (
+ {children}
+ );
+ }
+);