diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml index be0edcd..735acf5 100644 --- a/.github/workflows/deploy-storybook.yml +++ b/.github/workflows/deploy-storybook.yml @@ -39,4 +39,7 @@ jobs: run: yarn build - name: Deploy Storybook to GitHub Pages - run: npx gh-pages -d storybook-static -b storybook-ui + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npx gh-pages -d storybook-static -b storybook-ui -r https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git diff --git a/src/components/baseDialog/BaseDialog.tsx b/src/components/baseDialog/BaseDialog.tsx new file mode 100644 index 0000000..c75349f --- /dev/null +++ b/src/components/baseDialog/BaseDialog.tsx @@ -0,0 +1,116 @@ +import { IconWeight, X } from '@phosphor-icons/react'; + +export interface BaseDialogProps { + isOpen: boolean; + title?: string; + hideCloseButton?: boolean; + subTitle?: string; + dialogRounded?: boolean; + children: JSX.Element | JSX.Element[]; + classes?: string; + titleClasses?: string; + panelClasses?: string; + closeClass?: string; + weightIcon?: IconWeight; + bgColor?: string; + onClose: () => void; + dataTest?: string; +} + +/** + * BaseDialog component + * + * @property {boolean} isOpen + * - Controls whether the dialog is open or closed. If true, the dialog is visible. + * + * @property {string} [title] + * - The title of the dialog, displayed at the top of the dialog box. + * + * @property {boolean} [hideCloseButton] + * - If true, hides the close button (X icon) in the top right corner of the dialog. + * + * @property {string} [subTitle] + * - A subtitle for the dialog, displayed below the title. + * + * @property {boolean} [dialogRounded] + * - If true, applies a more rounded corner style to the dialog. + * + * @property {JSX.Element | JSX.Element[]} children + * - The content to be displayed inside the dialog. Can be a single JSX element or an array of elements. + * + * @property {string} [classes] + * - Custom classes for the outermost container of the dialog. Allows additional styling like margins or padding. + * + * @property {string} [titleClasses] + * - Custom classes for styling the title element. Can modify font size, weight, etc. + * + * @property {string} [panelClasses] + * - Custom classes for the main dialog panel, where the content is displayed. + * + * @property {string} [closeClass] + * - Custom classes for the close button, allowing for customization of the button's appearance. + * + * @property {IconWeight} [weightIcon] + * - Controls the thickness of the close button icon (X). Options range from "thin" to "bold". + * + * @property {string} [bgColor] + * - Custom background color for the dialog. Defaults to a light surface color if not provided. + * + * @property {() => void} onClose + * - Callback function triggered when the close button or overlay is clicked, used to close the dialog. + */ + +const BaseDialog = ({ + isOpen, + title, + subTitle, + dialogRounded, + children, + onClose, + classes, + panelClasses, + titleClasses, + closeClass, + weightIcon, + bgColor, + dataTest, + hideCloseButton, +}: BaseDialogProps): JSX.Element => { + return ( +
+
+
+ {title ? ( +
+ + {title} + + {subTitle} +
+ ) : null} + {hideCloseButton ? null : ( +
+ +
+ )} +
+ {children} +
+
+ ); +}; + +export default BaseDialog; diff --git a/src/components/baseDialog/__test__/BaseDialog.test.tsx b/src/components/baseDialog/__test__/BaseDialog.test.tsx new file mode 100644 index 0000000..1f81ee7 --- /dev/null +++ b/src/components/baseDialog/__test__/BaseDialog.test.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import BaseDialog from '../BaseDialog'; + +describe('BaseDialog', () => { + const onCloseMock = vi.fn(); + const defaultProps = { + title: 'Dialog Title', + subTitle: 'This is a subtitle', + isOpen: true, + onClose: onCloseMock, + children:
Dialog content
, + }; + const renderBaseDialog = (props = {}) => render(); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should match snapshot', () => { + const baseDialog = renderBaseDialog(); + expect(baseDialog).toMatchSnapshot(); + }); + + it('should render the dialog when isOpen is true', () => { + const { container } = renderBaseDialog(); + expect(container.firstChild).toHaveClass('flex'); + }); + + it('should not render the dialog when isOpen is false', () => { + const { container } = renderBaseDialog({ isOpen: false }); + expect(container.firstChild).toHaveClass('hidden'); + }); + + it('should trigger onClose when the close button is clicked', () => { + const { getByRole } = renderBaseDialog(); + const closeButton = getByRole('button'); + + fireEvent.click(closeButton); + expect(onCloseMock).toHaveBeenCalledTimes(1); + }); + + it('should apply custom class names correctly', () => { + const { container } = renderBaseDialog({ classes: 'custom-class' }); + expect(container.firstChild).toHaveClass('custom-class'); + }); + + it('should render the title and subtitle when provided', () => { + const { getByText } = renderBaseDialog(); + + expect(getByText('Dialog Title')).toBeInTheDocument(); + expect(getByText('This is a subtitle')).toBeInTheDocument(); + }); + + it('should not render the title div when title is not provided', () => { + const { container } = renderBaseDialog({ title: undefined }); + const titleDiv = container.querySelector('[title]'); + expect(titleDiv).not.toBeInTheDocument(); + }); + + it('should not render the subtitle when subTitle is not provided', () => { + const { container } = renderBaseDialog({ subTitle: undefined }); + const subTitleElement = container.querySelector('.justify-between.bg-gray-1.p-5'); + expect(subTitleElement).not.toBeInTheDocument(); + }); + + it('should render children content inside the dialog', () => { + const { getByText } = renderBaseDialog(); + expect(getByText('Dialog content')).toBeInTheDocument(); + }); + + it('should hide close button when hideCloseButton is true', () => { + const { queryByRole } = renderBaseDialog({ hideCloseButton: true }); + expect(queryByRole('button')).toBeNull(); + }); + + it('should apply the bgColor prop if provided', () => { + const { container } = renderBaseDialog({ bgColor: 'bg-red' }); + expect(container.firstChild?.firstChild).toHaveClass('bg-red'); + }); + + it('should render with rounded-2xl class when dialogRounded is true', () => { + const { container } = renderBaseDialog({ dialogRounded: true }); + const dialogPanel = container.querySelector('.rounded-2xl'); + expect(dialogPanel).toBeInTheDocument(); + }); + + it('should render with rounded-lg and pt-8 classes when dialogRounded is false', () => { + const { container } = renderBaseDialog({ dialogRounded: false }); + const dialogPanel = container.querySelector('.rounded-lg.pt-8'); + expect(dialogPanel).toBeInTheDocument(); + }); +}); diff --git a/src/components/baseDialog/__test__/__snapshots__/BaseDialog.test.tsx.snap b/src/components/baseDialog/__test__/__snapshots__/BaseDialog.test.tsx.snap new file mode 100644 index 0000000..831d09c --- /dev/null +++ b/src/components/baseDialog/__test__/__snapshots__/BaseDialog.test.tsx.snap @@ -0,0 +1,160 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`BaseDialog > should match snapshot 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+
+
+ + Dialog Title + + + This is a subtitle + +
+
+ + + +
+
+
+ Dialog content +
+
+
+
+ , + "container":
+
+
+
+
+ + Dialog Title + + + This is a subtitle + +
+
+ + + +
+
+
+ Dialog content +
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/index.ts b/src/components/index.ts index 34e9654..6f9fc96 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -15,3 +15,4 @@ export * from './list/List'; export * from './dropdown/Dropdown'; export * from './menu/Menu'; export * from './breadcrumbs/Breadcrumbs'; +export * from './baseDialog/BaseDialog'; diff --git a/src/stories/components/baseDialog/BaseDialog.stories.tsx b/src/stories/components/baseDialog/BaseDialog.stories.tsx new file mode 100644 index 0000000..ac6ce8d --- /dev/null +++ b/src/stories/components/baseDialog/BaseDialog.stories.tsx @@ -0,0 +1,61 @@ +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import BaseDialog, { BaseDialogProps } from '../../../components/baseDialog/BaseDialog'; +import { Button } from '../../../components'; + +const overlay: Decorator = (Story) => ( +
+ +
+); + +const meta: Meta = { + title: 'Components/BaseDialog', + component: BaseDialog, + parameters: { + layout: 'centered', + }, + decorators: [overlay], + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +const defaultArgs: BaseDialogProps = { + isOpen: true, + title: 'Dialog Title', + subTitle: 'Dialog Subtitle', + children: ( +
+
Dialog content
+ +
+ ), + onClose: () => alert('Dialog closed'), + bgColor: 'bg-white', + dialogRounded: true, + weightIcon: 'bold', +}; + +export const Default: Story = { + args: { + ...defaultArgs, + }, +}; + +export const WithoutCloseButton: Story = { + args: { + ...defaultArgs, + hideCloseButton: true, + }, +}; + +export const CustomStyles: Story = { + args: { + ...defaultArgs, + classes: 'p-6 shadow-lg', + panelClasses: 'border border-red', + titleClasses: 'font-semibold', + closeClass: 'border border-red text-green hover:text-red', + }, +};