Skip to content

Commit

Permalink
feat: Create component "Modal" (#230) (#67)
Browse files Browse the repository at this point in the history
### Changes made 
Explain what you made to present the following solution on this pull
request.

---

### My pull request is for
- [ ] A bugfix
- [x] A new component
- [ ] An existing component update

### Also, it complies with the following
- In case of a `new component`
  - [x] A folder with its name on `src/components/molecules`
  - [x] A `index.tsx` file where the component will be coded
- [x] New and/or updated interfaces, types, tuples and enums for the
component
- [x] A `index.test.ts` file for its units tests and create the needed
to reach at least 90% of code coverage
- [x] A `index.stories.tsx` file for storybook stories and add at least
2 stories for different scenarios

---

### Screenshots
`In case of have differences between old and new functionality, please
provide a 'Before vs After' comparrison in order to show in a graphic
way your contribution`
  • Loading branch information
NicolasOmar authored Dec 10, 2023
2 parents 9df3a41 + e5b0100 commit 15d5eac
Show file tree
Hide file tree
Showing 9 changed files with 2,072 additions and 2,501 deletions.
4,303 changes: 1,827 additions & 2,476 deletions package-lock.json

Large diffs are not rendered by default.

50 changes: 25 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"update:doctor": "npm run lint && npm run test && npm run build"
},
"devDependencies": {
"@babel/core": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@babel/core": "^7.23.5",
"@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@mdi/font": "^7.3.67",
Expand All @@ -64,48 +64,48 @@
"@semantic-release/commit-analyzer": "^11.1.0",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^9.2.3",
"@semantic-release/npm": "^11.0.1",
"@semantic-release/github": "^9.2.5",
"@semantic-release/npm": "^11.0.2",
"@semantic-release/release-notes-generator": "^12.1.0",
"@storybook/addon-actions": "^7.5.3",
"@storybook/addon-essentials": "^7.5.3",
"@storybook/addon-interactions": "^7.5.3",
"@storybook/addon-links": "^7.5.3",
"@storybook/addon-mdx-gfm": "^7.5.3",
"@storybook/cli": "^7.5.3",
"@storybook/react": "^7.5.3",
"@storybook/react-webpack5": "^7.5.3",
"@storybook/addon-actions": "^7.6.4",
"@storybook/addon-essentials": "^7.6.4",
"@storybook/addon-interactions": "^7.6.4",
"@storybook/addon-links": "^7.6.4",
"@storybook/addon-mdx-gfm": "^7.6.4",
"@storybook/cli": "^7.6.4",
"@storybook/react": "^7.6.4",
"@storybook/react-webpack5": "^7.6.4",
"@storybook/testing-library": "^0.2.2",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.8",
"@types/react": "^18.2.37",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@types/jest": "^29.5.11",
"@types/react": "^18.2.43",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"babel-jest": "^29.7.0",
"babel-loader": "^9.1.3",
"bulma": "^0.9.4",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.33.2",
"husky": "^8.0.3",
"hygen": "^6.2.11",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.1.0",
"lint-staged": "^15.2.0",
"npm-check-updates": "^16.14.11",
"postcss": "^8.4.31",
"postcss": "^8.4.32",
"prettier": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^4.5.0",
"rollup": "^4.7.0",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-postcss": "^4.0.2",
"semantic-release": "^22.0.8",
"storybook": "^7.5.3",
"semantic-release": "^22.0.10",
"storybook": "^7.6.4",
"tslib": "^2.6.2",
"typescript": "^5.3.2"
"typescript": "^5.3.3"
},
"lint-staged": {
"src/**/*.(ts|tsx)": [
Expand Down
1 change: 1 addition & 0 deletions src/components/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export { default as BreadcrumbItem } from './BreadcrumbItem'
export { default as DropdownTrigger } from './DropdownTrigger'
export { default as DropdownItem } from './DropdownItem'
export { default as MenuItem } from './MenuItem'
export { default as Image } from './Image'
export { default as PaginationItem } from './PaginationItem'
37 changes: 37 additions & 0 deletions src/components/molecules/Modal/index.mocks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"testing": {
"basicTestId": "test-modal-active",
"backgroundTestId": "test-modal-background",
"closeTestId": "test-modal-close",
"testChildren": "Hello There"
},
"storybook": {
"testButtonList": [
{
"text": "Hello",
"color": "is-info"
},
{
"text": "There",
"color": "is-success"
},
{
"text": "General",
"color": "is-warning"
},
{
"text": "Kenoby",
"color": "is-danger"
}
],
"testImageSrc": "https://bulma.io/images/placeholders/256x256.png",
"parameters": {
"componentSubtitle": "Here goes component's subtitle, a base idea about what it does",
"docs": {
"description": {
"component": "Here goes component's technical description, mostly based on Bulma's documentation."
}
}
}
}
}
49 changes: 49 additions & 0 deletions src/components/molecules/Modal/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useState } from 'react'
import { StoryFn, Meta } from '@storybook/react'
// COMPONENTS
import Modal from '.'
import ButtonGroup from '../ButtonGroup'
import { Button, Image } from '../../atoms'
// TYPES & INTERFACES
import { ButtonProps } from '../../../interfaces/atomProps'
// MOCKS
import { storybook } from './index.mocks.json'

export default {
title: 'Molecules/Modal',
component: Modal
} as Meta<typeof Modal>

const Template: StoryFn<typeof Modal> = args => {
const [modalIsOpen, setModalIsOpen] = useState<boolean>(false)
return (
<>
<Button
text='Click here to open the modal'
onClick={() => setModalIsOpen(!modalIsOpen)}
/>
{modalIsOpen && (
<Modal
{...{
...args,
onCloseClick: () => setModalIsOpen(!modalIsOpen)
}}
/>
)}
</>
)
}

export const BasicExample = Template.bind({})

export const ImageExample = Template.bind({})
ImageExample.args = {
children: <Image src={storybook.testImageSrc} />
}

export const ButtonGroupExample = Template.bind({})
ButtonGroupExample.args = {
children: (
<ButtonGroup buttonList={storybook.testButtonList as ButtonProps[]} />
)
}
57 changes: 57 additions & 0 deletions src/components/molecules/Modal/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
// COMPONENTS
import Modal from '.'
// TYPES & INTERFACES
// MOCKS
import { testing } from './index.mocks.json'

describe('Modal', () => {
const { basicTestId, testChildren, backgroundTestId, closeTestId } = testing

test('Should render the component', () => {
render(<Modal>{testChildren}</Modal>)
const testModal = screen.getByTestId(basicTestId)

expect(testModal).toBeInTheDocument()
})

test('Should not render the component if has no children', () => {
render(<Modal />)
expect(() => screen.getByTestId(basicTestId)).toThrow()
})

test('Should render the modal and close it when I click its close button', async () => {
const clickeableCloseConfig = {
children: testChildren,
onCloseClick: jest.fn()
}

render(<Modal {...clickeableCloseConfig} />)
const closeModalButton = screen.getByTestId(closeTestId)

expect(closeModalButton).toBeInTheDocument()
await userEvent.click(closeModalButton)

expect(clickeableCloseConfig.onCloseClick).toHaveBeenCalled()
expect(() => screen.getByTestId(basicTestId)).toThrow()
})

test('Should render the modal and close it when I select its background', async () => {
const clickeableCloseConfig = {
children: testChildren,
onCloseClick: jest.fn()
}

render(<Modal {...clickeableCloseConfig} />)
const backgroundModal = screen.getByTestId(backgroundTestId)

expect(backgroundModal).toBeInTheDocument()
await userEvent.click(backgroundModal)

expect(clickeableCloseConfig.onCloseClick).toHaveBeenCalled()
expect(() => screen.getByTestId(basicTestId)).toThrow()
})
})
68 changes: 68 additions & 0 deletions src/components/molecules/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from 'react'
// COMPONENTS
// TYPES & INTERFACES
import { ModalProps } from '../../../interfaces/moleculeProps'
// PARSERS
import { parseClasses, parseTestId } from '../../../functions/parsers'

const Modal: React.FC<ModalProps> = ({
testId = null,
containerTestId = null,
cssClasses = null,
containerCssClasses = null,
style = null,
containerStyle = null,
children = null,
onCloseClick = null
}) => {
const [modalIsOpen, setModalIsOpen] = useState<boolean>(true)
const modalContainerClasses = parseClasses([
'modal',
'is-active',
containerCssClasses
])
const modalContainerTestId =
containerTestId ??
parseTestId({ tag: 'modal', parsedClasses: modalContainerClasses })
const modalClasses = parseClasses(['modal-content', cssClasses])
const modalTestId =
testId ?? parseTestId({ tag: 'modal-content', parsedClasses: modalClasses })

const handleCloseClick = () => {
if (onCloseClick) onCloseClick()
setModalIsOpen(false)
}

return (
children &&
modalIsOpen && (
<section
data-testid={modalContainerTestId}
className={modalContainerClasses}
style={containerStyle ?? undefined}
>
<section
data-testid={'test-modal-background'}
className='modal-background'
aria-hidden='true'
onClick={handleCloseClick}
></section>
<section
data-testid={modalTestId}
className={modalClasses}
style={style ?? undefined}
>
{children}
</section>
<button
data-testid={'test-modal-close'}
className='modal-close is-large'
aria-label='close'
onClick={handleCloseClick}
></button>
</section>
)
)
}

export default Modal
1 change: 1 addition & 0 deletions src/components/molecules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as Message } from './Message'
export { default as Menu } from './Menu'
export { default as MenuList } from './MenuList'
export { default as Pagination } from './Pagination'
export { default as Modal } from './Modal'
7 changes: 7 additions & 0 deletions src/interfaces/moleculeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,10 @@ export interface PaginationProps extends ComposedElementProps {
/** `Styling` Will adjust the pages position on screen */
alignment?: elementAlignType | null
}

export interface ModalProps extends ComposedElementProps {
/** `Attribute` Reffers to the component or array of components that will be shown inside the column */
children?: string | React.ReactElement | React.ReactElement[] | null
/** `Function` Custom function related to the modal's close button to inject custom code if needed */
onCloseClick?: () => void
}

0 comments on commit 15d5eac

Please sign in to comment.