Skip to content
This repository has been archived by the owner on Jun 8, 2023. It is now read-only.

Commit

Permalink
feat: Buttons and icons (#18)
Browse files Browse the repository at this point in the history
- Wrapper component for listing multiple buttons in a responsive and styled manner.
- An Icon component that supports Font Awesome based icons.
  • Loading branch information
Tiernebre authored Jun 20, 2021
1 parent fd14dc5 commit 52d5bca
Show file tree
Hide file tree
Showing 21 changed files with 197 additions and 53 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
],
"version": "0.0.2",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.3",
"bulma": "^0.9.3",
"react": "^17.0.2",
"react-dom": "^17.0.2"
Expand Down
File renamed without changes.
20 changes: 4 additions & 16 deletions src/components/Button.tsx → src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
import { ButtonHTMLAttributes } from "react";
import { colors } from "../../types";

export const ButtonColors = [
"white",
"light",
"dark",
"black",
"text",
"ghost",
"primary",
"link",
"info",
"success",
"warning",
"danger",
] as const;

export const ButtonSizes = ["small", "normal", "medium", "large"];
export const ButtonColors = [...colors, "text", "ghost"] as const;

export const ButtonSizes = ["small", "normal", "medium", "large"] as const;

export type ButtonColor = typeof ButtonColors[number];
export type ButtonSize = typeof ButtonSizes[number];
Expand Down
1 change: 1 addition & 0 deletions src/components/button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Button";
18 changes: 18 additions & 0 deletions src/components/buttons/Buttons.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { render, screen } from "@testing-library/react";
import { Button } from "../button/Button";
import { Buttons } from "./Buttons";

it("renders child buttons", () => {
const firstText = "First Button";
const secondText = "Second Button";
render(
<Buttons>
<Button color="success">{firstText}</Button>
<Button color="danger">{secondText}</Button>
</Buttons>
);
const firstButton = screen.getByRole("button", { name: firstText });
const secondButton = screen.getByRole("button", { name: secondText });
expect(firstButton).toBeInTheDocument();
expect(secondButton).toBeInTheDocument();
});
7 changes: 7 additions & 0 deletions src/components/buttons/Buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { HTMLAttributes } from "react";

type ButtonsProps = HTMLAttributes<HTMLDivElement>;

export const Buttons = (props: ButtonsProps): JSX.Element => (
<div className="buttons">{props.children}</div>
);
1 change: 1 addition & 0 deletions src/components/buttons/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Buttons";
51 changes: 51 additions & 0 deletions src/components/icon/Icon.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { render, screen } from "@testing-library/react";
import { Color, colors } from "../../types";
import { Icon } from "./Icon";

it("renders an icon", () => {
const message = "Home";
render(<Icon name="home" message={message} />);
const icon = screen.getByTitle(message);
expect(icon).toBeInTheDocument();
});

it.each<Color>(colors)("renders an icon with color = %p", (color: Color) => {
const message = "Home";
render(<Icon name="home" message={message} color={color} />);
const icon = screen.getByTitle(message);
expect(icon).toHaveClass(`has-text-${color}`);
});

it("renders an icon with a border", () => {
const message = "Home";
render(<Icon name="home" message={message} bordered />);
const icon = screen.getByTitle(message);
expect(icon).toHaveClass("fa-border");
});

it("renders an icon without a border", () => {
const message = "Home";
render(<Icon name="home" message={message} bordered={false} />);
const icon = screen.getByTitle(message);
expect(icon).not.toHaveClass("fa-border");
});

it("renders an icon without a border by default", () => {
const message = "Home";
render(<Icon name="home" message={message} />);
const icon = screen.getByTitle(message);
expect(icon).not.toHaveClass("fa-border");
});

it("renders the message for screen readers only", () => {
const message = "Create Test";
render(<Icon name="plus" message={message} />);
const screenOnlyMessage = screen.getByText(message);
expect(screenOnlyMessage).toHaveClass("is-sr-only");
});

it("does not render the screen reader message if a message is not provided", () => {
render(<Icon name="minus" />);
const screenOnlyMessage = screen.queryByTestId("icon-screen-reader-message");
expect(screenOnlyMessage).toBeNull();
});
37 changes: 37 additions & 0 deletions src/components/icon/Icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Color } from "../../types";

export type IconProps = {
name: string; // https://fontawesome.com/v5/cheatsheet displays the possible names
message?: string;
color?: Color;
bordered?: boolean;
};

export const Icon = ({
name,
color,
message,
bordered,
}: IconProps): JSX.Element => {
const classes = [`fas fa-${name.toLowerCase()}`];

if (color) {
classes.push(`has-text-${color}`);
}
if (bordered) {
classes.push("fa-border");
}

const className = classes.join(" ");

return (
<span className="icon">
<i aria-hidden="true" title={message} className={className}></i>
{message && (
<span className="is-sr-only" data-testid="icon-screen-reader-message">
{message}
</span>
)}
</span>
);
};
1 change: 1 addition & 0 deletions src/components/icon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Icon";
4 changes: 3 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./Button";
export * from "./button";
export * from "./buttons";
export * from "./icon";
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./components";
export * from "./types";
7 changes: 1 addition & 6 deletions src/stories/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { Story, Meta } from "@storybook/react";

import {
Button,
ButtonColors,
ButtonProps,
ButtonSizes,
} from "../components/Button";
import { Button, ButtonColors, ButtonProps, ButtonSizes } from "../components";

export default {
title: "Example/Button",
Expand Down
19 changes: 19 additions & 0 deletions src/stories/Buttons.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Story, Meta } from "@storybook/react";

import { Button, Buttons } from "../components";

export default {
title: "Example/Buttons",
component: Buttons,
} as Meta;

const Template: Story = () => (
<Buttons>
<Button color="success">Submit</Button>
<Button color="danger">Cancel</Button>
<Button color="warning">Re-Do</Button>
</Buttons>
);

export const DefaultButtons = Template.bind({});
DefaultButtons.args = {};
30 changes: 30 additions & 0 deletions src/stories/Icon.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Story, Meta } from "@storybook/react";

import { Icon, IconProps } from "../components";
import { colors } from "../types";

export default {
title: "Example/Icon",
component: Icon,
argTypes: {
name: {
control: {
type: "text",
},
},
color: {
control: {
type: "select",
options: colors,
},
},
},
} as Meta;

const Template: Story<IconProps> = (args) => <Icon {...args} />;

export const InteractiveIcon = Template.bind({});
InteractiveIcon.args = {
name: "home",
message: "Go Home",
};
30 changes: 0 additions & 30 deletions src/stories/button.css

This file was deleted.

1 change: 1 addition & 0 deletions src/styles/theme.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@charset "utf-8";
@import "../../node_modules/bulma/bulma.sass";
@import "../../node_modules/@fortawesome/fontawesome-free/css/all.css";
14 changes: 14 additions & 0 deletions src/types/color/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const colors = [
"white",
"light",
"dark",
"black",
"primary",
"link",
"info",
"success",
"warning",
"danger",
] as const;

export type Color = typeof colors[number];
1 change: 1 addition & 0 deletions src/types/color/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./colors";
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./color";
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"

"@fortawesome/fontawesome-free@^5.15.3":
version "5.15.3"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz#c36ffa64a2a239bf948541a97b6ae8d729e09a9a"
integrity sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w==

"@hapi/[email protected]":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
Expand Down

0 comments on commit 52d5bca

Please sign in to comment.