✨(react) implement Button with official tokens
Now that we have all the official design tokens we can use them to build the button component in various colors matching the design system's ones.
This commit is contained in:
@@ -1,10 +1,72 @@
|
||||
.c__button {
|
||||
background-image: var(--c--theme--colors--primary-gradient);
|
||||
padding: 8px 30px;
|
||||
border-radius: var(--c--components--button--border-radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
box-shadow: var(--c--components--button--shadow);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--c--theme--transitions--duration) var(--c--theme--transitions--ease-out);
|
||||
|
||||
padding: 0 var(--c--theme--spacings--s);
|
||||
|
||||
height: var(--c--components--button--height);
|
||||
border-radius: var(--c--components--button--border-radius);
|
||||
font-size: var(--c--components--button--font-size);
|
||||
font-weight: var(--c--components--button--font-weight);
|
||||
|
||||
&--primary {
|
||||
background-color: var(--c--theme--colors--primary-500);
|
||||
color: var(--c--theme--colors--primary-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--primary-600);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--c--theme--colors--primary-500);
|
||||
}
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background-color: var(--c--theme--colors--secondary-500);
|
||||
color: var(--c--theme--colors--secondary-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--secondary-600);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--c--theme--colors--secondary-500);
|
||||
}
|
||||
}
|
||||
|
||||
&--tertiary {
|
||||
background-color: transparent;
|
||||
color: var(--c--theme--colors--greyscale-800);
|
||||
|
||||
&:hover {
|
||||
color: var(--c--theme--colors--greyscale-900);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--c--theme--colors--greyscale-800);
|
||||
}
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background-color: var(--c--theme--colors--danger-500);
|
||||
color: var(--c--theme--colors--danger-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--c--theme--colors--danger-600);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--c--theme--colors--danger-500);
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--c--theme--colors--greyscale-200);
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { buildTheme, loadTokens } from "tests/Theme";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Button } from "./index";
|
||||
|
||||
describe("<Button/>", () => {
|
||||
@@ -11,10 +12,33 @@ describe("<Button/>", () => {
|
||||
expect(button.classList.contains("c__button")).toBe(true);
|
||||
});
|
||||
|
||||
it("call onClick when click occurs", async () => {
|
||||
const user = userEvent.setup();
|
||||
const handleClick = vi.fn();
|
||||
render(<Button onClick={handleClick}>Test button</Button>);
|
||||
const button = screen.getByRole("button", { name: "Test button" });
|
||||
expect(handleClick).not.toBeCalled();
|
||||
user.click(button);
|
||||
await waitFor(() => expect(handleClick).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it("does not call onClick when click occurs on a disabled button", async () => {
|
||||
const user = userEvent.setup();
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<Button onClick={handleClick} disabled={true}>
|
||||
Test button
|
||||
</Button>
|
||||
);
|
||||
const button = screen.getByRole("button", { name: "Test button" });
|
||||
expect(handleClick).not.toBeCalled();
|
||||
await act(async () => user.click(button));
|
||||
expect(handleClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses custom token", async () => {
|
||||
await buildTheme();
|
||||
const tokens = await loadTokens();
|
||||
expect(tokens.components.button["border-radius"]).toBeDefined();
|
||||
expect(tokens.components.button.shadow).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,27 @@ export default {
|
||||
|
||||
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
children: "Amazing button",
|
||||
export const Primary = Template.bind({});
|
||||
Primary.args = {
|
||||
children: "Label",
|
||||
color: "primary",
|
||||
};
|
||||
|
||||
export const Secondary = Template.bind({});
|
||||
Secondary.args = {
|
||||
children: "Label",
|
||||
color: "secondary",
|
||||
};
|
||||
|
||||
export const Tertiary = Template.bind({});
|
||||
Tertiary.args = {
|
||||
children: "Label",
|
||||
color: "tertiary",
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
children: "Label",
|
||||
color: "primary",
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import React, { ButtonHTMLAttributes } from "react";
|
||||
|
||||
interface Props extends PropsWithChildren {}
|
||||
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
color?: "primary" | "secondary" | "tertiary";
|
||||
}
|
||||
|
||||
export const Button = ({ children }: Props) => {
|
||||
return <button className="c__button">{children}</button>;
|
||||
export const Button = ({ children, color = "primary", ...props }: Props) => {
|
||||
return (
|
||||
<button className={"c__button c__button--" + color} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,9 @@ import { DefaultTokens } from "@openfun/cunningham-tokens";
|
||||
|
||||
export const tokens = (defaults: DefaultTokens) => {
|
||||
return {
|
||||
"border-radius": "5px",
|
||||
shadow: "0px 0px 10px 1px " + defaults.theme.colors.primary + ";",
|
||||
"border-radius": "2px",
|
||||
height: "48px",
|
||||
"font-size": defaults.theme.typo.l,
|
||||
"font-weight": defaults.theme.typo.medium,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
@import "@fontsource/roboto/100";
|
||||
@import "@fontsource/roboto/300";
|
||||
@import "@fontsource/roboto/400";
|
||||
@import "@fontsource/roboto/500";
|
||||
@import "@fontsource/roboto/700";
|
||||
@import "@fontsource/roboto/900";
|
||||
@import "cunningham-tokens";
|
||||
@import '@openfun/cunningham-tokens/default-tokens';
|
||||
@import './components/Button';
|
||||
@import './components/Button';
|
||||
|
||||
* {
|
||||
font-family: var(--c--theme--typo--font-base);
|
||||
}
|
||||
Reference in New Issue
Block a user