Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

71 Delete Task #79

Merged
merged 11 commits into from
Dec 5, 2024
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
46 changes: 46 additions & 0 deletions src/common/components/Dialog/Backdrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { PropsWithChildren } from 'react';
import { BaseComponentProps } from '@leanstacks/react-common';
import classNames from 'classnames';

/**
* Properties for the `Backdrop` component.
* @param {function} [onClick] - Optional. A function called when clicked.
* @see {@link BaseComponentProps}
* @see {@link PropsWithChildren}
*/
interface BackdropProps extends BaseComponentProps, PropsWithChildren {
onClick?: (e: React.MouseEvent) => void | Promise<void>;
}

/**
* The `Backdrop` component renders a semi-opaque background for another
* component. Usually used to partially mask background content to draw
* attention to content in the foreground such as a `Dialog` or `Menu`.
* @param {BackgroundProps} props - Component properties
* @returns {JSX.Element} JSX
*/
const Backdrop = ({
children,
className,
onClick,
testId = 'backdrop',
}: BackdropProps): JSX.Element => {
const handleClick = (e: React.MouseEvent) => {
onClick?.(e);
};

return (
<div
className={classNames(
'fixed right-0 top-0 z-[1000] h-screen w-screen bg-neutral-500/50',
className,
)}
onClick={handleClick}
data-testid={testId}
>
{children}
</div>
);
};

export default Backdrop;
69 changes: 69 additions & 0 deletions src/common/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { PropsWithChildren, useEffect, useState } from 'react';
import { BaseComponentProps } from '@leanstacks/react-common';
import classNames from 'classnames';

import Backdrop from './Backdrop';

/**
* Properties for the `Dialog` component.
* @param {boolean} [isOpen] - Indicates if the Dialog should be displayed.
* @param {function} [onClose] - A function called when the Dialog closes.
* @see {@link BaseComponentProps}
* @see {@link PropsWithChildren}
*/
export interface DialogProps extends BaseComponentProps, PropsWithChildren {
isOpen?: boolean;
onClose?: () => void | Promise<void>;
}

/**
* A `Dialog` is a modal window that displays on top of the main content,
* typically asking the user to take an action or confirm a decision.
* @param {DialogProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const Dialog = ({
children,
className,
isOpen = false,
onClose,
testId = 'dialog',
}: DialogProps): JSX.Element => {
const [isDialogOpen, setIsDialogOpen] = useState(false);

useEffect(() => {
setIsDialogOpen(isOpen);
}, [isOpen]);

const closeDialog = (): void => {
setIsDialogOpen(false);
onClose?.();
};

const handleBackdropClick = (): void => {
closeDialog();
};

const handleDialogClick = (e: React.MouseEvent): void => {
e.stopPropagation();
};

return (
<div className={classNames({ hidden: !isDialogOpen }, className)} data-testid={testId}>
<Backdrop
className="flex items-center justify-center"
onClick={handleBackdropClick}
testId={`${testId}-backdrop`}
>
<div
className="m-4 min-w-72 max-w-[560px] rounded-3xl bg-light-bg p-6 dark:bg-dark-bg"
onClick={handleDialogClick}
>
{children}
</div>
</Backdrop>
</div>
);
};

export default Dialog;
45 changes: 45 additions & 0 deletions src/common/components/Dialog/DialogButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Button, ButtonProps, ButtonVariant } from '@leanstacks/react-common';
import classNames from 'classnames';

/**
* Variations of the `DialogButton`.
*/
type DialogButtonVariant = 'primary' | 'secondary' | 'danger';

/**
* Properties for the `DialogButton` component.
* @param {DialogButtonVariant} variant - The variant.
* @see {@link ButtonProps}
*/
interface DialogButtonProps extends Omit<ButtonProps, 'variant'> {
variant?: DialogButtonVariant;
}

/**
* The `DialogButton` is a type of `Button` specifically styled for use
* within a `Dialog`.
* @param {DialogButtonProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const DialogButton = ({
className,
variant = 'secondary',
testId = 'dialog-button',
...buttonProps
}: DialogButtonProps): JSX.Element => {
return (
<Button
variant={ButtonVariant.Text}
className={classNames(
'text-sm',
{ 'font-bold text-blue-600 dark:text-blue-400': variant === 'primary' },
{ 'font-bold text-red-600': variant === 'danger' },
className,
)}
testId={testId}
{...buttonProps}
/>
);
};

export default DialogButton;
34 changes: 34 additions & 0 deletions src/common/components/Dialog/DialogButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { PropsWithChildren } from 'react';
import { BaseComponentProps } from '@leanstacks/react-common';
import classNames from 'classnames';

/**
* Properties for the `DialogButtons` component.
* @see {@link BaseComponentProps}
* @see {@link PropsWithChildren}
*/
interface DialogButtonsProps extends BaseComponentProps, PropsWithChildren {}

/**
* The `DialogButtons` component is a container for one or more `DialogButton`
* components. `DialogButtons` renders the buttons, the `children`, styled
* appropriately for appearing within a `Dialog`.
* @param {DialogButtonsProps} props - Component properties
* @returns {JSX.Element} JSX
*/
const DialogButtons = ({
children,
className,
testId = 'dialog-buttons',
}: DialogButtonsProps): JSX.Element => {
return (
<div
className={classNames('mt-6 flex items-center justify-end gap-4', className)}
data-testid={testId}
>
{children}
</div>
);
};

export default DialogButtons;
30 changes: 30 additions & 0 deletions src/common/components/Dialog/DialogContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PropsWithChildren } from 'react';
import { BaseComponentProps } from '@leanstacks/react-common';
import classNames from 'classnames';

/**
* Properties for the `DialogContent` component.
* @see {@link BaseComponentProps}
* @see {@link PropsWithChildren}
*/
interface DialogContentProps extends BaseComponentProps, PropsWithChildren {}

/**
* The `DialogContent` component serves as the container for the main content
* of a `Dialog`. All of the main body content should be wrapped by `DialogContent`.
* @param {DialogContentProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const DialogContent = ({
children,
className,
testId = 'dialog-content',
}: DialogContentProps): JSX.Element => {
return (
<div className={classNames('mb-6 mt-4 text-sm', className)} data-testid={testId}>
{children}
</div>
);
};

export default DialogContent;
30 changes: 30 additions & 0 deletions src/common/components/Dialog/DialogHeading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PropsWithChildren } from 'react';
import { BaseComponentProps } from '@leanstacks/react-common';
import classNames from 'classnames';

/**
* Properties for the `DialogHeading` component.
* @see {@link BaseComponentProps}
* @see {@link PropsWithChildren}
*/
interface DialogHeadingProps extends BaseComponentProps, PropsWithChildren {}

/**
* The `DialogHeading` component serves as the container for the heading content
* of a `Dialog`. All of the heading content should be wrapped by `DialogHeading`.
* @param {DialogHeadingProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const DialogHeading = ({
children,
className,
testId = 'dialog-heading',
}: DialogHeadingProps): JSX.Element => {
return (
<div className={classNames('mb-4 line-clamp-2 text-2xl', className)} data-testid={testId}>
{children}
</div>
);
};

export default DialogHeading;
25 changes: 25 additions & 0 deletions src/common/components/Dialog/Divider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BaseComponentProps } from '@leanstacks/react-common';
import classNames from 'classnames';

/**
* Properties for the `Divider` component.
* @see {@link BaseComponentProps}
*/
interface DividerProps extends BaseComponentProps {}

/**
* The `Divider` component renders a horizontal line which visually separates
* content.
* @param {DividerProps} props - Component properties.
* @returns {JSX.Element} JSX
*/
const Divider = ({ className, testId = 'divider' }: DividerProps): JSX.Element => {
return (
<div
className={classNames('h-0 w-full border-t border-neutral-500/50', className)}
data-testid={testId}
></div>
);
};

export default Divider;
50 changes: 50 additions & 0 deletions src/common/components/Dialog/__tests__/Backdrop.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';

import { render, screen } from 'test/test-utils';

import Backdrop from '../Backdrop';

describe('Backdrop', () => {
it('should render successfully', async () => {
// ARRANGE
render(<Backdrop />);
await screen.findByTestId('backdrop');

// ASSERT
expect(screen.getByTestId('backdrop')).toBeDefined();
});

it('should use testid', async () => {
// ARRANGE
render(<Backdrop testId="my-backdrop" />);
await screen.findByTestId('my-backdrop');

// ASSERT
expect(screen.getByTestId('my-backdrop')).toBeDefined();
});

it('should use className', async () => {
// ARRANGE
render(<Backdrop className="my-class" />);
await screen.findByTestId('backdrop');

// ASSERT
expect(screen.getByTestId('backdrop')).toHaveClass('my-class');
});

it('should handle click', async () => {
// ARRANGE
const user = userEvent.setup();
const clickFn = vi.fn();
render(<Backdrop onClick={clickFn} />);
await screen.findByTestId('backdrop');

// ACT
await user.click(screen.getByTestId('backdrop'));

// ASSERT
expect(screen.getByTestId('backdrop')).toBeDefined();
expect(clickFn).toHaveBeenCalledOnce();
});
});
47 changes: 47 additions & 0 deletions src/common/components/Dialog/__tests__/DiaglogButtons.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest';

import { render, screen } from 'test/test-utils';

import DialogButtons from '../DialogButtons';

describe('DialogButtons', () => {
it('should render successfully', async () => {
// ARRANGE
render(
<DialogButtons>
<div data-testid="children"></div>
</DialogButtons>,
);
await screen.findByTestId('dialog-buttons');

// ASSERT
expect(screen.getByTestId('dialog-buttons')).toBeDefined();
expect(screen.getByTestId('children')).toBeDefined();
});

it('should use testId', async () => {
// ARRANGE
render(
<DialogButtons testId="my-dialog-buttons">
<div data-testid="children"></div>
</DialogButtons>,
);
await screen.findByTestId('my-dialog-buttons');

// ASSERT
expect(screen.getByTestId('my-dialog-buttons')).toBeDefined();
});

it('should use className', async () => {
// ARRANGE
render(
<DialogButtons className="my-class">
<div data-testid="children"></div>
</DialogButtons>,
);
await screen.findByTestId('dialog-buttons');

// ASSERT
expect(screen.getByTestId('dialog-buttons')).toHaveClass('my-class');
});
});
Loading
Loading