diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f47a111..07222dcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- new icons:
- `state-confirmed-all`
- `state-declined-all`
+- ``
+ - added `outlined` property
### Fixed
diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx
index b0923489..f8b8d661 100644
--- a/src/components/Tag/Tag.tsx
+++ b/src/components/Tag/Tag.tsx
@@ -10,6 +10,9 @@ import { TestIconProps } from "../Icon/TestIcon";
import decideContrastColorValue from "./../../common/utils/colorDecideContrastvalue";
+const WHITE = '#FFFFFF';
+const BLACK = '#000000';
+
export interface TagProps
extends Omit<
BlueprintTagProps,
@@ -22,11 +25,16 @@ export interface TagProps
* Sets the background color of a tag, depends on the `Color` object provided by the
* [npm color module](https://www.npmjs.com/export package/color) v3. You can use it with
* all allowed [CSS color values](https://developer.mozilla.org/de/docs/Web/CSS/color_value).
- *
- * The front color is set automatically, so the tag label is always readable.
+ * When `outlined` is true, it becomes the border. When `outlined` is false, it behaves like expected (fills the background).
*/
backgroundColor?: Color | string;
+ /**
+ * Display tag with outlined style — transparent background with only a colored border.
+ * Works with `backgroundColor`, `intent`, or default colors for the border styling.
+ */
+ outlined?: boolean;
+
/**
* visual appearance and "thickness" of the tag
*/
@@ -59,25 +67,51 @@ function Tag({
small = false,
large = false,
backgroundColor,
+ color,
+ outlined = false,
...otherProps
}: TagProps) {
- otherProps["interactive"] = otherProps.interactive ?? !!otherProps.onClick ? true : false;
- if (backgroundColor) {
- const additionalStyles = otherProps.style ?? {};
- let color = Color("#ffffff");
+ otherProps["interactive"] = otherProps.interactive ?? !!otherProps.onClick;
+
+ const additionalStyles = otherProps.style ?? {};
+
+ if (outlined) {
+ let colorObj = Color(BLACK);
try {
- color = Color(backgroundColor);
- } catch (ex) {
+ colorObj = Color(backgroundColor);
+ } catch (ex: unknown) {
// eslint-disable-next-line no-console
console.warn("Received invalid background color for tag: " + backgroundColor);
}
+ otherProps["style"] = {
+ ...additionalStyles,
+ borderColor: colorObj.rgb().toString(),
+ color: colorObj.rgb().toString()
+ };
+ } else if (!outlined && backgroundColor) {
+ let backgroundObj = Color(WHITE);
+
+ try {
+ backgroundObj = Color(backgroundColor);
+ } catch {
+ // eslint-disable-next-line no-console
+ console.warn("Received invalid background color for tag: " + backgroundColor);
+ }
+
+ let colorObj = Color(decideContrastColorValue({ testColor: backgroundObj }));
+ if (color) {
+ try {
+ colorObj = Color(color);
+ } catch {
+ // eslint-disable-next-line no-console
+ console.warn("Received invalid color for tag: " + color);
+ }
+ }
otherProps["style"] = {
...additionalStyles,
- ...{
- backgroundColor: color.rgb().toString(),
- color: decideContrastColorValue({ testColor: color }),
- },
+ backgroundColor: backgroundObj.rgb().toString(),
+ color: colorObj.rgb().toString(),
};
}
const leftIcon = !!icon && typeof icon === "string" ? : icon;
@@ -89,6 +123,7 @@ function Tag({
(intent ? ` ${intentClassName(intent)}` : "") +
(small ? ` ${eccgui}-tag--small` : "") +
(large ? ` ${eccgui}-tag--large` : "") +
+ (outlined ? ` ${eccgui}-tag--outlined` : "") +
(className ? " " + className : "")
}
minimal={minimal}
diff --git a/src/components/Tag/tag.scss b/src/components/Tag/tag.scss
index 8abfb2e1..7f56d0d1 100644
--- a/src/components/Tag/tag.scss
+++ b/src/components/Tag/tag.scss
@@ -272,6 +272,12 @@ $tag-round-adjustment: 0 !default;
}
}
+.#{$ns}-tag.#{$eccgui}-tag--outlined,
+.#{$ns}-tag.#{$ns}-minimal.#{$eccgui}-tag--outlined {
+ background-color: transparent;
+ border-color: currentcolor;
+}
+
@media print {
.#{$eccgui}-tag__item {
print-color-adjust: exact;
diff --git a/src/components/Tag/tests/Tag.test.tsx b/src/components/Tag/tests/Tag.test.tsx
new file mode 100644
index 00000000..87b0e7e0
--- /dev/null
+++ b/src/components/Tag/tests/Tag.test.tsx
@@ -0,0 +1,107 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+
+import "@testing-library/jest-dom";
+
+import { Default } from "../stories/Tag.stories";
+import Tag from "../Tag";
+
+const eccgui = "eccgui";
+
+describe("Tag", () => {
+ it("renders tag content", () => {
+ render(Tag label);
+ expect(screen.getByText("Tag label")).toBeInTheDocument();
+ });
+
+ it("always has base class and default emphasis class", () => {
+ const { container } = render(label);
+ const tag = container.firstChild as HTMLElement;
+ expect(tag).toHaveClass(`${eccgui}-tag__item`);
+ expect(tag).toHaveClass(`${eccgui}-tag--normalemphasis`);
+ });
+
+ it("applies small class when small prop is set", () => {
+ const { container } = render(label);
+ expect(container.firstChild).toHaveClass(`${eccgui}-tag--small`);
+ });
+
+ it("applies large class when large prop is set", () => {
+ const { container } = render(label);
+ expect(container.firstChild).toHaveClass(`${eccgui}-tag--large`);
+ });
+
+ it("applies correct emphasis class", () => {
+ const { container } = render(label);
+ expect(container.firstChild).toHaveClass(`${eccgui}-tag--strongeremphasis`);
+ });
+
+ it("applies intent class when intent prop is set", () => {
+ const { container } = render(label);
+ expect(container.firstChild).toHaveClass(`${eccgui}-intent--primary`);
+ });
+
+ it("applies outlined class when outlined prop is set", () => {
+ const { container } = render(label);
+ expect(container.firstChild).toHaveClass(`${eccgui}-tag--outlined`);
+ });
+
+ it("does not apply outlined class by default", () => {
+ const { container } = render(label);
+ expect(container.firstChild).not.toHaveClass(`${eccgui}-tag--outlined`);
+ });
+
+ describe("backgroundColor", () => {
+ it("sets background-color and text color inline styles", () => {
+ const { container } = render(label);
+ const tag = container.firstChild as HTMLElement;
+ expect(tag.style.backgroundColor).toBeTruthy();
+ expect(tag.style.color).toBeTruthy();
+ });
+
+ it("does not set inline background-color when outlined", () => {
+ const { container } = render(label);
+ const tag = container.firstChild as HTMLElement;
+ expect(tag.style.backgroundColor).toBeFalsy();
+ });
+
+ it("sets border-color from backgroundColor when outlined", () => {
+ const { container } = render(label);
+ const tag = container.firstChild as HTMLElement;
+ expect(tag.style.borderColor).toBeTruthy();
+ });
+
+ it("sets black border-color by default when outlined without backgroundColor", () => {
+ const { container } = render(label);
+ const tag = container.firstChild as HTMLElement;
+ expect(tag.style.borderColor).toBe("rgb(0, 0, 0)");
+ });
+
+ it("does not set any inline color style when no backgroundColor is provided", () => {
+ const { container } = render(label);
+ const tag = container.firstChild as HTMLElement;
+ expect(tag.style.backgroundColor).toBeFalsy();
+ expect(tag.style.color).toBeFalsy();
+ expect(tag.style.borderColor).toBeFalsy();
+ });
+ });
+
+ describe("icon", () => {
+ it("renders icon when icon prop is a string", () => {
+ const { container } = render(label);
+ expect(container.getElementsByClassName(`${eccgui}-icon`).length).toBeGreaterThan(0);
+ });
+ });
+
+ describe("interactive", () => {
+ it("is not interactive by default", () => {
+ const { container } = render(label);
+ expect(container.firstChild).not.toHaveClass("bp5-interactive");
+ });
+
+ it("is interactive when onClick is provided", () => {
+ const { container } = render(label);
+ expect(container.firstChild).toHaveClass("bp5-interactive");
+ });
+ });
+});