Implement new Icon Button component

Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
This commit is contained in:
Tarik Gürsoy
2024-04-11 16:31:13 +02:00
parent 56f335468d
commit fdb6f2cd42
3 changed files with 131 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
- type: added
description: New component "icon button" in ui library

View File

@@ -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<ComponentProps<typeof ExternalLinkButt
<ExternalLinkButtonComponent {...args} />
);
const IconButtonTemplate: StoryFn<ComponentProps<typeof IconButtonComponent>> = (args) => (
<IconButtonComponent {...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 = <Icon className="is-small">trash</Icon>;
const mediumIcon = <Icon className="is-medium">trash</Icon>;
/*
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",
};

View File

@@ -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<HTMLAnchorElement, ExternalLi
</ExternalLink>
)
);
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<HTMLButtonElement> & {
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<HTMLButtonElement, IconButtonProps>(
({ 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" ? (
<StyledIconButtonCircle {...elementAttributes}>{children}</StyledIconButtonCircle>
) : (
<StyledIconButton {...elementAttributes}>{children}</StyledIconButton>
);
}
);