Skip to content

Commit

Permalink
feat(Pill): introducing Pill component
Browse files Browse the repository at this point in the history
  • Loading branch information
aversini committed Feb 27, 2024
1 parent ccde108 commit 2e393f1
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 3 deletions.
14 changes: 14 additions & 0 deletions lib/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export const tokens = {
"surface-light": colors.slate[300],
"surface-lighter": colors.slate[200],
"surface-accent": "#0B93F6",
"surface-information": colors.violet[200],
"surface-success": colors.green[200],
"surface-warning": colors.orange[200],
"surface-error": colors.red[200],

/**
* Typography tokens.
Expand All @@ -47,6 +51,11 @@ export const tokens = {
"copy-error-dark": errorColorDark,
"copy-error-light": errorColorLight,

"copy-information": colors.violet[800],
"copy-success": colors.green[800],
"copy-warning": colors.orange[800],
"copy-error": colors.red[800],

/**
* Border tokens.
*/
Expand All @@ -58,6 +67,11 @@ export const tokens = {
"border-error-dark": errorColorDark,
"border-error-light": errorColorLight,

"border-information": colors.violet[400],
"border-success": colors.green[400],
"border-warning": colors.orange[400],
"border-error": colors.red[400],

/**
* Focus tokens.
*/
Expand Down
10 changes: 7 additions & 3 deletions packages/documentation/.ladle/components.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "./styles.css";

import { ButtonIcon, Footer } from "@versini/ui-components";
import { ButtonIcon, Footer, Pill } from "@versini/ui-components";
import { Flexgrid, FlexgridItem } from "@versini/ui-system";

import type { GlobalProvider } from "@ladle/react";
Expand All @@ -11,12 +11,16 @@ const renderImportLine = (importName: string, stage?: string) => {
const releaseTag = stage ? stage : "alpha";
return (
<div className="mb-6">
<Flexgrid alignVertical="flex-end" className="mb-2">
<Flexgrid
alignVertical="flex-end"
alignHorizontal="space-between"
className="mb-2"
>
<FlexgridItem>
<h1 className="m-0">{importName}</h1>
</FlexgridItem>
<FlexgridItem>
<p className="m-0 pl-2">stage: {releaseTag}</p>
<Pill label={releaseTag} theme="information" />
</FlexgridItem>
</Flexgrid>

Expand Down
38 changes: 38 additions & 0 deletions packages/documentation/src/Components/Pill.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Story } from "@ladle/react";
import { Pill } from "@versini/ui-components";

export default {
title: "Components/Pill",
meta: {
importName: "Pill",
},
};

export const Basic: Story<any> = (args) => {
return (
<div className="flex flex-wrap gap-2">
<Pill label="this is a pill" {...args} />
</div>
);
};
Basic.args = {
description: "",
};
Basic.argTypes = {
variant: {
options: ["error", "information", "success", "warning"],
control: { type: "radio" },
defaultValue: "information",
},
};

export const HardCoded: Story<any> = () => {
return (
<div className="flex flex-wrap gap-2">
<Pill label="warning" variant="warning" />
<Pill label="success" variant="success" />
<Pill label="info" variant="information" />
<Pill label="error" variant="error" />
</div>
);
};
2 changes: 2 additions & 0 deletions packages/ui-components/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ export const BUBBLE_CLASSNAME = "av-bubble";

export const FLEXGRID_MAX_COLUMNS = 12;
export const FLEXGRID_GAP_RATIO = 0.25;

export const PILL_CLASSNAME = "av-pill";
23 changes: 23 additions & 0 deletions packages/ui-components/src/components/Pill/Pill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { PillProps } from "./PillTypes";
import { getPillClasses } from "./utilities";

export const Pill = ({
label,
className,
variant = "information",
description,
spacing,
}: PillProps) => {
const pillClassName = getPillClasses({
label,
className,
variant,
spacing,
});
return (
<div role="text" className={pillClassName}>
{description && <span className="sr-only">{description}</span>}
<span>{label}</span>
</div>
);
};
23 changes: 23 additions & 0 deletions packages/ui-components/src/components/Pill/PillTypes.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { SpacingProps } from "@versini/ui-private/dist/utilities";

export type PillProps = {
/**
* Content of the Pill.
*/
label: string;
/**
* CSS class(es) to add to the main component wrapper.
*/
className?: string;
/**
* Hidden label adjacent to the pill text to provide added
* context for screen reader users, ideally no more
* than 2-3 words.
*/
description?: string;
/**
* Theme of the Pill.
* @default "information"
*/
variant?: "information" | "warning" | "error" | "success";
} & SpacingProps;
82 changes: 82 additions & 0 deletions packages/ui-components/src/components/Pill/__tests__/Pill.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { render, screen } from "@testing-library/react";

import { expectToHaveClasses } from "../../../common/__tests__/helpers";
import { PILL_CLASSNAME } from "../../../common/constants";
import { Pill } from "../..";

describe("Pill (exceptions)", () => {
it("should be able to require/import from root", () => {
expect(Pill).toBeDefined();
});
});

describe("Pill modifiers", () => {
it("should render a default pill", async () => {
render(<Pill label="hello" />);
const pill = await screen.findByRole("text");
expectToHaveClasses(pill, [
PILL_CLASSNAME,
"px-2",
"py-0.5",
"text-xs",
"bg-surface-information",
"text-copy-information",
"border-border-information",
]);
});

it("should render a pill with variant warning", async () => {
render(<Pill label="hello" variant="warning" />);
const pill = await screen.findByRole("text");
expectToHaveClasses(pill, [
PILL_CLASSNAME,
"px-2",
"py-0.5",
"text-xs",
"bg-surface-warning",
"text-copy-warning",
"border-border-warning",
]);
});

it("should render a pill with variant success", async () => {
render(<Pill label="hello" variant="success" />);
const pill = await screen.findByRole("text");
expectToHaveClasses(pill, [
PILL_CLASSNAME,
"px-2",
"py-0.5",
"text-xs",
"bg-surface-success",
"text-copy-success",
"border-border-success",
]);
});

it("should render a pill with variant error", async () => {
render(<Pill label="hello" variant="error" />);
const pill = await screen.findByRole("text");
expectToHaveClasses(pill, [
PILL_CLASSNAME,
"px-2",
"py-0.5",
"text-xs",
"bg-surface-error",
"text-copy-error",
"border-border-error",
]);
});

it("should render a pill with a custom class", async () => {
render(<Pill label="hello" className="custom-class" />);
const pill = await screen.findByRole("text");
expectToHaveClasses(pill, [PILL_CLASSNAME, "custom-class"]);
});

it("should render a pill with a description", async () => {
render(<Pill label="hello" description="this is a description" />);
await screen.findByRole("text");
const description = await screen.findByText("this is a description");
expect(description.className).toContain("sr-only");
});
});
46 changes: 46 additions & 0 deletions packages/ui-components/src/components/Pill/utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getSpacing } from "@versini/ui-private/dist/utilities";
import clsx from "clsx";

import { PILL_CLASSNAME } from "../../common/constants";
import type { PillProps } from "./PillTypes";

const getPillBorderClasses = ({ variant }: { variant?: string }) => {
return clsx("rounded-sm border", {
"border-border-information": variant === "information",
"border-border-warning": variant === "warning",
"border-border-success": variant === "success",
"border-border-error": variant === "error",
});
};

const getPillCopyClasses = ({ variant }: { variant?: string }) => {
return clsx("not-prose", {
"text-copy-information": variant === "information",
"text-copy-warning": variant === "warning",
"text-copy-success": variant === "success",
"text-copy-error": variant === "error",
});
};

const getPillBackgroundClasses = ({ variant }: { variant?: string }) => {
return clsx({
"bg-surface-information": variant === "information",
"bg-surface-warning": variant === "warning",
"bg-surface-success": variant === "success",
"bg-surface-error": variant === "error",
});
};

export const getPillClasses = (props: PillProps) => {
const { className, spacing, variant } = props;

return clsx(
PILL_CLASSNAME,
"px-2 py-0.5 text-xs",
getSpacing(spacing),
getPillBorderClasses({ variant }),
getPillCopyClasses({ variant }),
getPillBackgroundClasses(props),
className,
);
};
4 changes: 4 additions & 0 deletions packages/ui-components/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Menu } from "./Menu/Menu";
import { MenuItem } from "./Menu/MenuItem";
import { MenuSeparator } from "./Menu/MenuItem";
import { Panel } from "./Panel/Panel";
import { Pill } from "./Pill/Pill";
import { Spinner } from "./Spinner/Spinner";
import {
Table,
Expand All @@ -31,6 +32,8 @@ import { TextInput } from "./TextInput/TextInput";
import { TextInputMask } from "./TextInput/TextInputMask";
import { Toggle } from "./Toggle/Toggle";

Pill;

export {
Anchor,
Bubble,
Expand All @@ -45,6 +48,7 @@ export {
MenuItem,
MenuSeparator,
Panel,
Pill,
Spinner,
Table,
TableBody,
Expand Down

0 comments on commit 2e393f1

Please sign in to comment.