Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
- `<Tag />`
- added `outlined` property

### Fixed

Expand Down
59 changes: 47 additions & 12 deletions src/components/Tag/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
*/
Expand Down Expand Up @@ -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 name={icon} /> : icon;
Expand All @@ -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}
Expand Down
6 changes: 6 additions & 0 deletions src/components/Tag/tag.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
107 changes: 107 additions & 0 deletions src/components/Tag/tests/Tag.test.tsx
Original file line number Diff line number Diff line change
@@ -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 {...Default.args}>Tag label</Tag>);
expect(screen.getByText("Tag label")).toBeInTheDocument();
});

it("always has base class and default emphasis class", () => {
const { container } = render(<Tag>label</Tag>);
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(<Tag small>label</Tag>);
expect(container.firstChild).toHaveClass(`${eccgui}-tag--small`);
});

it("applies large class when large prop is set", () => {
const { container } = render(<Tag large>label</Tag>);
expect(container.firstChild).toHaveClass(`${eccgui}-tag--large`);
});

it("applies correct emphasis class", () => {
const { container } = render(<Tag emphasis="stronger">label</Tag>);
expect(container.firstChild).toHaveClass(`${eccgui}-tag--strongeremphasis`);
});

it("applies intent class when intent prop is set", () => {
const { container } = render(<Tag intent="primary">label</Tag>);
expect(container.firstChild).toHaveClass(`${eccgui}-intent--primary`);
});

it("applies outlined class when outlined prop is set", () => {
const { container } = render(<Tag outlined>label</Tag>);
expect(container.firstChild).toHaveClass(`${eccgui}-tag--outlined`);
});

it("does not apply outlined class by default", () => {
const { container } = render(<Tag>label</Tag>);
expect(container.firstChild).not.toHaveClass(`${eccgui}-tag--outlined`);
});

describe("backgroundColor", () => {
it("sets background-color and text color inline styles", () => {
const { container } = render(<Tag backgroundColor="#ff0000">label</Tag>);
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(<Tag outlined backgroundColor="#ff0000">label</Tag>);
const tag = container.firstChild as HTMLElement;
expect(tag.style.backgroundColor).toBeFalsy();
});

it("sets border-color from backgroundColor when outlined", () => {
const { container } = render(<Tag outlined backgroundColor="#ff0000">label</Tag>);
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(<Tag outlined>label</Tag>);
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(<Tag>label</Tag>);
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(<Tag icon="item-info">label</Tag>);
expect(container.getElementsByClassName(`${eccgui}-icon`).length).toBeGreaterThan(0);
});
});

describe("interactive", () => {
it("is not interactive by default", () => {
const { container } = render(<Tag>label</Tag>);
expect(container.firstChild).not.toHaveClass("bp5-interactive");
});

it("is interactive when onClick is provided", () => {
const { container } = render(<Tag onClick={jest.fn()}>label</Tag>);
expect(container.firstChild).toHaveClass("bp5-interactive");
});
});
});
Loading