Skip to content
Merged
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ This project uses GitHub Actions to perform DevOps automation activities such as
- [React Hook Form][reacthookform]
- [Yup][yup]
- [Tailwind CSS][tailwind]
- [Class Variance Authority][cva]
- [Font Awesome][fontawesome]
- [React Spring][reactspring]
- [React i18next][reacti18next]
Expand All @@ -281,6 +282,7 @@ This project uses GitHub Actions to perform DevOps automation activities such as
[reacthookform]: https://www.react-hook-form.com/ 'React Hook Form'
[yup]: https://github.com/jquense/yup 'Yup'
[tailwind]: https://tailwindcss.com/ 'Tailwind CSS'
[cva]: https://cva.style/ 'Class Variance Authority'
[fontawesome]: https://fontawesome.com/ 'Font Awesome'
[tanstack]: https://tanstack.com/ 'TanStack'
[testing-library]: https://testing-library.com/ 'Testing Library'
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@tanstack/react-query-devtools": "5.66.2",
"@tanstack/react-table": "8.21.2",
"axios": "1.7.9",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"dayjs": "1.11.13",
"i18next": "24.2.2",
Expand Down
57 changes: 36 additions & 21 deletions src/common/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,71 @@
import { ButtonHTMLAttributes } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';

import { PropsWithTestId } from 'common/utils/types';
import { cn } from 'common/utils/css';

/**
* The variations of `Button` components.
* Define the component base and variant styles.
*/
export type ButtonVariant = 'solid' | 'outline' | 'text';
const variants = cva(
'flex items-center justify-center gap-2 rounded-md border enabled:hover:opacity-80 disabled:opacity-50 cursor-pointer',
{
variants: {
variant: {
solid:
'border-neutral-700 bg-neutral-700 text-white dark:border-neutral-300 dark:bg-neutral-300 dark:text-neutral-900',
outline: 'border-neutral-700 dark:border-neutral-300',
text: 'border-transparent',
},
size: {
sm: 'h-9 px-3 text-sm',
md: 'h-10 px-4',
lg: 'h-11 px-8',
icon: 'h-auto p-0',
},
},
defaultVariants: { variant: 'solid', size: 'md' },
},
);

/**
* The variant attributes of the Button component.
*/
type ButtonVariants = VariantProps<typeof variants>;

/**
* Properties for the `Button` component.
* @param {ButtonVariant} [variant] - Optional. The type of Button. Default: `solid`
* @see {@link ButtonHTMLAttributes}
* @see {@link ButtonVariants}
* @see {@link PropsWithTestId}
*/
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, PropsWithTestId {
variant?: ButtonVariant;
}
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
ButtonVariants,
PropsWithTestId {}

/**
* The `Button` React component formats and renders a styled button.
* @param {ButtonProps} props - Component properties, `ButtonProps`.
* @returns {JSX.Element} JSX
*/
const Button = ({
children,
className,
role = 'button',
size,
type = 'button',
variant = 'solid',
variant,
testId = 'button',
...props
}: ButtonProps): JSX.Element => {
return (
<button
className={cn(
'flex items-center justify-center gap-2 rounded-md border px-2 py-1 enabled:hover:opacity-80 disabled:opacity-50',
{
'border-neutral-700 bg-neutral-700 text-white dark:border-neutral-300 dark:bg-neutral-300 dark:text-neutral-900':
variant === 'solid',
'border-neutral-700 dark:border-neutral-300': variant === 'outline',
'border-transparent': variant === 'text',
},
className,
)}
className={cn(variants({ size, variant, className }))}
data-testid={testId}
role={role}
type={type}
{...props}
>
{children}
</button>
/>
);
};

Expand Down
8 changes: 4 additions & 4 deletions src/common/components/Button/LanguageToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,24 @@ const LanguageToggle = ({ className }: LanguageToggleProps): JSX.Element => {
return (
<Dropdown
toggle={
<Button variant="text">
<Button variant="text" size="icon">
<FAIcon icon="language" size="2x" title="Select Language" />
</Button>
}
content={
<DropdownContent className="text-sm">
<DropdownItem onClick={() => setLanguage('en')} testId="dropdown-item-en">
<Button variant="text" className="p-0!" title="English Language">
<Button variant="text" className="h-auto p-0!" title="English Language">
English
</Button>
</DropdownItem>
<DropdownItem onClick={() => setLanguage('fr')} testId="dropdown-item-fr">
<Button variant="text" className="p-0!" title="French Language">
<Button variant="text" className="h-auto p-0!" title="French Language">
French
</Button>
</DropdownItem>
<DropdownItem onClick={() => setLanguage('es')} testId="dropdown-item-es">
<Button variant="text" className="p-0!" title="Spanish Language">
<Button variant="text" className="h-auto p-0!" title="Spanish Language">
Spanish
</Button>
</DropdownItem>
Expand Down
2 changes: 2 additions & 0 deletions src/common/components/Button/ThemeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const ThemeToggle = ({ className }: ThemeToggleProps): JSX.Element => {
{settings?.theme === 'light' ? (
<Button
variant="text"
size="icon"
className={cn('text-light-text', className)}
title="Dark Mode"
onClick={() => setSettings({ theme: 'dark' })}
Expand All @@ -36,6 +37,7 @@ const ThemeToggle = ({ className }: ThemeToggleProps): JSX.Element => {
) : (
<Button
variant="text"
size="icon"
className={cn('text-dark-text', className)}
title="Light Mode"
onClick={() => setSettings({ theme: 'light' })}
Expand Down
18 changes: 17 additions & 1 deletion src/common/components/Button/__stories__/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const meta = {
},
},
className: { description: 'Additional CSS classes.' },
variant: { description: 'The variant.' },
size: { description: 'The size variant.' },
variant: { description: 'The style variant.' },
testId: { description: 'The test identifier.' },
},
args: {
Expand Down Expand Up @@ -61,5 +62,20 @@ export const IconButton: Story = {
args: {
children: 'Icon',
variant: 'text',
size: 'icon',
},
};

export const Small: Story = {
args: {
variant: 'solid',
size: 'sm',
},
};

export const Large: Story = {
args: {
variant: 'solid',
size: 'lg',
},
};
2 changes: 1 addition & 1 deletion src/common/components/Dialog/DialogButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ const DialogButton = ({
return (
<Button
variant="text"
size="sm"
className={cn(
'text-sm',
{ 'font-bold text-blue-600 dark:text-blue-400': variant === 'primary' },
{ 'font-bold text-red-600': variant === 'danger' },
className,
Expand Down
4 changes: 2 additions & 2 deletions src/common/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ const Header = ({ testId = 'header' }: HeaderProps): JSX.Element => {

return (
<header
className="flex h-16 items-center justify-between border-b border-b-neutral-500 border-opacity-30 bg-neutral-100 px-4 dark:border-opacity-50 dark:bg-neutral-900"
className="border-opacity-30 dark:border-opacity-50 flex h-16 items-center justify-between border-b border-b-neutral-500 bg-neutral-100 px-4 dark:bg-neutral-900"
data-testid={testId}
>
<div className="flex items-center">
<Link to={isAuthenticated ? '/app/tasks' : '/'}>
<img src={logo} alt="Logo" height="32" width="32" />
</Link>
</div>
<div className="flex items-center">
<div className="flex items-center gap-4">
<LanguageToggle />
<ThemeToggle />
<MenuButton Menu={AppMenu} />
Expand Down
1 change: 1 addition & 0 deletions src/common/components/Menu/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const MenuButton = ({
<>
<Button
variant="text"
size="icon"
className={cn('text-light-text dark:text-dark-text', className)}
onClick={() => setIsMenuOpen(true)}
title={title}
Expand Down
3 changes: 2 additions & 1 deletion src/common/components/Menu/MenuCloseButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const MenuCloseButton = ({
return (
<Button
variant="text"
className={cn('ps-1 pe-1 hover:bg-neutral-200 dark:hover:bg-neutral-200/25', className)}
size="icon"
className={cn('size-8 hover:bg-neutral-200 dark:hover:bg-neutral-200/25', className)}
onClick={() => close?.()}
title="Close"
testId={testId}
Expand Down
2 changes: 1 addition & 1 deletion src/common/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const Toast = ({ className, dismiss, testId = 'toast', toast }: ToastProps): JSX
</div>
<Button
variant="text"
className="p-0!"
size="icon"
onClick={() => doDismiss()}
data-testid={`${testId}-button-dismiss`}
>
Expand Down
29 changes: 28 additions & 1 deletion src/pages/Components/components/ButtonComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Text from 'common/components/Text/Text';
import { ComponentProperty } from '../model/components';
import { createColumnHelper } from '@tanstack/react-table';
import Table from 'common/components/Table/Table';
import FAIcon from 'common/components/Icon/FAIcon';

/**
* Properties for the `ButtonComponents` React component.
Expand Down Expand Up @@ -35,13 +36,17 @@ const ButtonComponents = ({
name: 'onClick',
description: 'Optional. Click event handler function.',
},
{
name: 'size',
description: 'Optional. Size variant. Default: md',
},
{
name: 'testId',
description: 'Optional. Identifier for testing.',
},
{
name: 'variant',
description: 'Optional. Applies default styling. Default: solid',
description: 'Optional. Style variant. Default: solid',
},
];
const columnHelper = createColumnHelper<ComponentProperty>();
Expand Down Expand Up @@ -84,6 +89,28 @@ const ButtonComponents = ({
<CodeSnippet className="my-2" code={`<Button>Default button</Button>`} />
</div>

<div className="my-8">
<div className="mb-2 flex items-center justify-center gap-8 rounded-sm border border-neutral-500/10 p-4 dark:bg-neutral-700/25">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="icon" variant="text">
<FAIcon icon="bars" size="xl" />
</Button>
</div>
<CodeSnippet
className="my-2"
code={`<>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="icon" variant="text">
<FAIcon icon="bars" size="xl" />
</Button>
</>`}
/>
</div>

<div className="my-8">
<div className="mb-2 flex place-content-center rounded-sm border border-neutral-500/10 p-4 dark:bg-neutral-700/25">
<Button variant="outline">Outline button</Button>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Tasks/TasksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ const TasksPage = ({ testId = 'page-tasks' }: PropsWithTestId): JSX.Element => {
{/* page heading */}
<div className="mb-4 flex items-center justify-between border-b border-neutral-500/50 pb-2">
<h1 className="text-4xl">{t('tasks', { ns: 'tasks' })}</h1>
<div className="flex items-center gap-2">
<div className="flex items-center gap-4">
<Button
variant="text"
size="icon"
title="Add task"
onClick={() => navigate('/app/tasks/add')}
data-testid={`${testId}-button-add`}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Tasks/components/Edit/TaskCompleteToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ const TaskCompleteToggle = ({

return (
<Button
className={cn('m-0! contents border-none! p-0!', className)}
className={cn('contents border-none!', className)}
variant="text"
size="icon"
title={buttonTitle}
onClick={handleButtonClick}
onMouseEnter={() => setIsHovering(true)}
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Tasks/components/Form/TaskForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,19 @@ const TaskForm = ({
testId={`${testId}-input-completed`}
/>

<div className="flex items-center gap-4">
<div className="my-8 flex items-center gap-4">
<Button
type="button"
variant="outline"
className="my-8 w-1/2 sm:w-40"
className="w-1/2 sm:w-40"
onClick={onCancel}
disabled={formState.isSubmitting}
aria-label={t('label.cancel')}
testId={`${testId}-button-cancel`}
>
{t('label.cancel')}
</Button>

<Button
type="submit"
className="w-1/2 sm:w-40"
Expand Down
Loading