diff --git a/tools/ui-components/src/button.test.tsx b/tools/ui-components/src/button.test.tsx
deleted file mode 100644
index 739ff92d59..0000000000
--- a/tools/ui-components/src/button.test.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import React from 'react';
-import { Button } from './button';
-
-const onClick = jest.fn();
-
-describe('Button', () => {
- it("should have the role 'button' and the correct text", () => {
- render();
- expect(
- screen.getByRole('button', { name: /hello world/i })
- ).toBeInTheDocument();
- });
-
- it('should trigger the onClick prop on click', () => {
- render();
-
- const button = screen.getByRole('button', { name: /hello world/i });
-
- userEvent.click(button);
-
- expect(onClick).toHaveBeenCalledTimes(1);
- });
-});
diff --git a/tools/ui-components/src/button.tsx b/tools/ui-components/src/button.tsx
deleted file mode 100644
index f127f986a9..0000000000
--- a/tools/ui-components/src/button.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { ButtonProps } from './button.types';
-
-import './button.css';
-
-/**
- * Primary UI component for user interaction
- */
-export const Button: React.FC = ({
- primary,
- size = 'medium',
- label,
- ...props
-}: ButtonProps) => {
- const mode = primary
- ? 'storybook-button--primary'
- : 'storybook-button--secondary';
- return (
-
- );
-};
diff --git a/tools/ui-components/src/button.types.ts b/tools/ui-components/src/button.types.ts
deleted file mode 100644
index 9c57f74bfd..0000000000
--- a/tools/ui-components/src/button.types.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-type ButtonSize = 'small' | 'medium' | 'large';
-
-export interface ButtonProps {
- primary?: boolean;
- size?: ButtonSize;
- label: string;
- customKey?: string;
- onClick: () => void;
-}
diff --git a/tools/ui-components/src/button.css b/tools/ui-components/src/button/button.css
similarity index 100%
rename from tools/ui-components/src/button.css
rename to tools/ui-components/src/button/button.css
diff --git a/tools/ui-components/src/button.stories.tsx b/tools/ui-components/src/button/button.stories.tsx
similarity index 57%
rename from tools/ui-components/src/button.stories.tsx
rename to tools/ui-components/src/button/button.stories.tsx
index 12f8758210..cd84ad4c03 100644
--- a/tools/ui-components/src/button.stories.tsx
+++ b/tools/ui-components/src/button/button.stories.tsx
@@ -1,7 +1,7 @@
import { Story } from '@storybook/react';
import React from 'react';
-import { Button } from './button';
-import { ButtonProps } from './button.types';
+
+import { Button, ButtonProps } from '.';
const story = {
title: 'Example/Button',
@@ -12,27 +12,27 @@ const Template: Story = args => {
return ;
};
-export const Primary = Template.bind({});
-Primary.args = {
- primary: true,
- label: 'Button'
+export const Default = Template.bind({});
+Default.args = {
+ children: 'Button'
};
-export const Secondary = Template.bind({});
-Secondary.args = {
- label: 'Button'
+export const Danger = Template.bind({});
+Danger.args = {
+ variant: 'danger',
+ children: 'Button'
};
export const Large = Template.bind({});
Large.args = {
size: 'large',
- label: 'Button'
+ children: 'Button'
};
export const Small = Template.bind({});
Small.args = {
size: 'small',
- label: 'Button'
+ children: 'Button'
};
export default story;
diff --git a/tools/ui-components/src/button/button.test.tsx b/tools/ui-components/src/button/button.test.tsx
new file mode 100644
index 0000000000..a88012339c
--- /dev/null
+++ b/tools/ui-components/src/button/button.test.tsx
@@ -0,0 +1,43 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { Button } from './button';
+
+describe('Button', () => {
+ it("should have the role 'button' and render the correct text", () => {
+ render();
+
+ expect(
+ screen.getByRole('button', { name: /hello world/i })
+ ).toBeInTheDocument();
+ });
+
+ it("should have the type 'button' by default", () => {
+ render();
+
+ expect(
+ screen.getByRole('button', { name: /hello world/i })
+ ).toHaveAttribute('type', 'button');
+ });
+
+ it("should have the type 'submit' if it is specified", () => {
+ render();
+
+ expect(
+ screen.getByRole('button', { name: /hello world/i })
+ ).toHaveAttribute('type', 'submit');
+ });
+
+ it('should trigger the onClick prop on click', () => {
+ const onClick = jest.fn();
+
+ render();
+
+ const button = screen.getByRole('button', { name: /hello world/i });
+
+ userEvent.click(button);
+
+ expect(onClick).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/tools/ui-components/src/button/button.tsx b/tools/ui-components/src/button/button.tsx
new file mode 100644
index 0000000000..4c668be9c6
--- /dev/null
+++ b/tools/ui-components/src/button/button.tsx
@@ -0,0 +1,65 @@
+import React, { useMemo } from 'react';
+import { ButtonProps, ButtonSize, ButtonVariant } from './types';
+
+const defaultClassNames = ['cursor-pointer', 'inline-block', 'border-3'];
+
+const computeClassNames = ({
+ size,
+ variant
+}: {
+ size: ButtonSize;
+ variant: ButtonVariant;
+}) => {
+ const classNames = [...defaultClassNames];
+
+ // TODO: support 'link' variant
+ switch (variant) {
+ case 'danger':
+ classNames.push(
+ 'border-default-foreground-danger',
+ 'bg-default-background-danger',
+ 'text-default-foreground-danger'
+ );
+ break;
+ // default variant is 'primary'
+ default:
+ classNames.push(
+ 'border-default-foreground-secondary',
+ 'bg-default-background-quaternary',
+ 'text-default-foreground-secondary'
+ );
+ }
+
+ switch (size) {
+ case 'large':
+ classNames.push('px-4 py-2.5 text-lg');
+ break;
+ case 'small':
+ classNames.push('px-2.5 py-1 text-sm');
+ break;
+ // default size is 'medium'
+ default:
+ classNames.push('px-3 py-1.5 text-md');
+ }
+
+ return classNames.join(' ');
+};
+
+export const Button = ({
+ variant = 'primary',
+ size = 'medium',
+ type = 'button',
+ onClick,
+ children
+}: ButtonProps) => {
+ const classes = useMemo(
+ () => computeClassNames({ size, variant }),
+ [size, variant]
+ );
+
+ return (
+
+ );
+};
diff --git a/tools/ui-components/src/button/index.ts b/tools/ui-components/src/button/index.ts
new file mode 100644
index 0000000000..2b28f14478
--- /dev/null
+++ b/tools/ui-components/src/button/index.ts
@@ -0,0 +1,2 @@
+export { Button } from './button';
+export type { ButtonProps } from './types';
diff --git a/tools/ui-components/src/button/types.ts b/tools/ui-components/src/button/types.ts
new file mode 100644
index 0000000000..417bceb0a3
--- /dev/null
+++ b/tools/ui-components/src/button/types.ts
@@ -0,0 +1,13 @@
+import { MouseEventHandler } from 'react';
+
+export type ButtonVariant = 'primary' | 'danger';
+
+export type ButtonSize = 'small' | 'medium' | 'large';
+
+export interface ButtonProps extends React.HTMLAttributes {
+ children: React.ReactNode;
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ onClick?: MouseEventHandler;
+ type?: 'submit' | 'button';
+}
diff --git a/tools/ui-components/src/colors.css b/tools/ui-components/src/colors.css
index 0c663d75d7..1df8889430 100644
--- a/tools/ui-components/src/colors.css
+++ b/tools/ui-components/src/colors.css
@@ -95,10 +95,13 @@ div.light {
--default-foreground-secondary: var(--gray85);
--default-foreground-tertiary: var(--gray80);
--default-foreground-quaternary: var(--gray75);
+ --default-foreground-danger: var(--red15);
+
--default-background-primary: var(--gray00);
--default-background-secondary: var(--gray05);
--default-background-tertiary: var(--gray10);
--default-background-quaternary: var(--gray15);
+ --default-background-danger: var(--red90);
}
html.dark,
@@ -106,8 +109,11 @@ div.dark {
--default-foreground-primary: var(--gray00);
--default-foreground-secondary: var(--gray05);
--default-foreground-quaternary: var(--gray15);
+ --default-foreground-danger: var(--red90);
+
--default-background-primary: var(--gray90);
--default-background-secondary: var(--gray85);
--default-background-tertiary: var(--gray80);
--default-background-quaternary: var(--gray75);
+ --default-background-danger: var(--red15);
}
diff --git a/tools/ui-components/tailwind.config.js b/tools/ui-components/tailwind.config.js
index 267b770fb1..03c7744a21 100644
--- a/tools/ui-components/tailwind.config.js
+++ b/tools/ui-components/tailwind.config.js
@@ -15,10 +15,12 @@ module.exports = {
'default-foreground-secondary': 'var(--default-foreground-secondary)',
'default-foreground-tertiary': 'var(--default-foreground-tertiary)',
'default-foreground-quaternary': 'var(--default-foreground-quaternary)',
+ 'default-foreground-danger': 'var(--default-foreground-danger)',
'default-background-primary': 'var(--default-background-primary)',
'default-background-secondary': 'var(--default-background-secondary)',
'default-background-tertiary': 'var(--default-background-tertiary)',
'default-background-quaternary': 'var(--default-background-quaternary)',
+ 'default-background-danger': 'var(--default-background-danger)',
green: {
50: 'var(--green05)',
100: 'var(--green10)',
@@ -52,6 +54,16 @@ module.exports = {
800: 'var(--red80)',
900: 'var(--red90)'
}
+ },
+ borderWidth: {
+ 3: '3px'
+ },
+ fontSize: {
+ // https://tailwindcss.com/docs/font-size#providing-a-default-line-height
+ // [fontSize, lineHeight]
+ sm: ['16px', '1.5'],
+ md: ['18px', '1.42857143'],
+ lg: ['24px', '1.3333333']
}
},
plugins: []