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
1 change: 1 addition & 0 deletions .github/workflows/sync-develop.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Sync main into develop

on:
workflow_call:
push:
tags:
- 'v*'
Expand Down
3 changes: 1 addition & 2 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import type { StorybookConfig } from "@storybook/react-webpack5";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-webpack5-compiler-swc",
"@storybook/addon-onboarding",
"@storybook/addon-webpack5-compiler-babel",
"@storybook/addon-links",
"@storybook/addon-a11y",
"@storybook/addon-docs"
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Below is a list of the components available in this library. Each component has
- [BBBHint](./src/components/Hint/README.md)
- [BBBModal](./src/components/Modal//README.md)
- [BBBNavigation](./src/components/Navigation/README.md)
- [BBBSearch](./src/components/Search/README.md)
- [BBBSelect](./src/components/Select/README.md)
- [BBBSpinner](./src/components/Spinner//README.md)
- [BBBTextAreaInput](./src/components/TextAreaInput/README.md)
Expand All @@ -25,13 +26,13 @@ Below is a list of the components available in this library. Each component has
This library requires the following peer dependencies to be installed in your project:

```
react >=18.0.0
react-dom >=18.0.0
react ^18.2.0
react-dom ^18.2.0
styled-components >=5.3.0
react-modal >=3.16.1
@emotion/react ^11.14.0
@emotion/styled ^11.14.1
@mui/material ^7.3.1
@emotion/react ^11.13.0
@emotion/styled ^11.13.0
@mui/material ^6.1.4
@mui/styles ^6.4.8
prop-types ^15.8.1
react-icons ^5.5.0
Expand Down
6,403 changes: 3,478 additions & 2,925 deletions package-lock.json

Large diffs are not rendered by default.

33 changes: 18 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@
]
},
"peerDependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.1",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@mui/material": "^6.1.4",
"@mui/styles": "^6.4.8",
"@mui/icons-material": "^7.3.1",
"@tippyjs/react": "^4.2.6",
"prop-types": "^15.8.1",
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"react-modal": ">=3.16.1",
"styled-components": ">=5.3.0",
Expand All @@ -56,13 +56,10 @@
"@babel/preset-typescript": "^7.24.7",
"@babel/register": "^7.24.6",
"@eslint/js": "^9.33.0",
"@storybook/addon-a11y": "^9.1.5",
"@storybook/addon-actions": "^9.0.8",
"@storybook/addon-docs": "^9.1.5",
"@storybook/addon-links": "^9.1.5",
"@storybook/addon-onboarding": "^9.1.5",
"@storybook/addon-webpack5-compiler-swc": "^4.0.1",
"@storybook/react-webpack5": "^9.1.5",
"@storybook/addon-docs": "^8.6.18",
"@storybook/addon-links": "^8.6.18",
"@storybook/addon-webpack5-compiler-babel": "^3.0.5",
"@storybook/react-webpack5": "^8.6.18",
"@types/react-modal": "^3.16.3",
"@types/react-toggle": "^4.0.5",
"@types/styled-components": "^5.1.34",
Expand All @@ -77,17 +74,23 @@
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-storybook": "^9.1.5",
"eslint-plugin-storybook": "^0.11.6",
"globals": "^16.3.0",
"path": "^0.12.7",
"storybook": "^9.1.5",
"storybook": "^8.6.18",
"@storybook/addon-a11y": "^8.6.18",
"@storybook/addon-actions": "^8.6.18",
"style-loader": "^4.0.0",
"styled-components": "6.1.13",
"ts-loader": "^9.5.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.40.0",
"webpack": "5.94.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.2.3"
"webpack-dev-server": "5.0.4"
},
"dependencies": {
"@mui/icons-material": "^6.1.4",
"react-modal": ">=3.16.1"
}
}
2 changes: 1 addition & 1 deletion src/components/Button/component.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from 'storybook/test';
import { fn } from '@storybook/test';
import BBButton from './component';
import { MdFavorite, MdMoreVert, MdSettings } from 'react-icons/md';
import {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Modal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import { BBBModal } from 'bbb-ui-components-react';
| `noFooter` | `boolean` | `false` | Hides the modal footer. |
| `footerContent` | `React.ReactNode` | `null` | Custom content for the footer. |
| `stickyFooter` | `boolean` | `true` | Makes the footer sticky. |
| `testId` | `string` | — | Test identifier applied to the modal element (native `ReactModal` prop). |
| `closeButtonDataTest` | `string` | — | Test identifier applied to the close button. Omitted if empty. |
| `children` | `React.ReactNode` | — | Modal content. |
| `...props` | `ReactModal.Props` | — | Any other props are passed down to the underlying `react-modal` instance. |

Expand Down
125 changes: 118 additions & 7 deletions src/components/Modal/component.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import React from 'react';
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import BBBModal from './component';
import { BBBTypography } from '../Typography';
import { BBButton } from '../Button';
import { BBBAccordion } from '../Accordion';
import { BBBSelect } from '../Select';
import { BBBCheckbox } from '../Checkbox';
import { BBBTextInput } from '../TextInput';
import { BBBHint } from '../Hint';
import MenuItem from '@mui/material/MenuItem';

const meta = {
title: 'BBBModal',
component: BBBModal,
tags: ['autodocs'],
parameters: {
// Prevent the fixed overlay from covering Storybook's own UI
layout: 'centered',
},
argTypes: {
isOpen: {
control: 'boolean',
Expand Down Expand Up @@ -56,6 +67,14 @@ const meta = {
control: 'boolean',
description: 'When true, the footer is sticky to the bottom.',
},
testId: {
control: 'text',
description: 'Test identifier applied to the modal element (native ReactModal prop).',
},
closeButtonDataTest: {
control: 'text',
description: 'Test identifier applied to the close button. Omitted if empty.',
},
children: {
control: false,
description: 'Modal body content.',
Expand All @@ -66,15 +85,107 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;

/**
* Wrapper component so hooks can be used inside Storybook's render function.
* The modal is controlled via a trigger button, keeping the controls panel
* accessible. Adjust args and re-open the modal to see the changes.
*/
const ModalStory: React.FC<React.ComponentProps<typeof BBBModal>> = (args) => {
const [isOpen, setIsOpen] = useState(args.isOpen);

// Sync with the Storybook "isOpen" control
React.useEffect(() => {
setIsOpen(args.isOpen);
}, [args.isOpen]);

return (
<>
<BBButton
label="Open Modal"
onClick={() => setIsOpen(true)}
/>
<BBBModal
{...args}
isOpen={isOpen}
onRequestClose={() => setIsOpen(false)}
footerContent={args.footerContent ?? <ModalFooter onClose={() => setIsOpen(false)} />}
ariaHideApp={false}
>
{args.children}
</BBBModal>
</>
);
};

const LOREM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;

const ModalFooter = ({ onClose }: { onClose: () => void }) => (
<>
<BBButton label="Cancel" variant="subtle" onClick={onClose} ariaLabel="Cancel" />
<BBButton label="Confirm" variant="primary" onClick={onClose} ariaLabel="Confirm" />
</>
);

const ModalBody = () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<BBBTypography variant="header">Profile Settings</BBBTypography>
<BBBTypography>{LOREM}</BBBTypography>

<BBBTextInput label="Full name" placeholder="John Doe" />

<BBBSelect title="Role" value="admin">
<MenuItem value="admin">Admin</MenuItem>
<MenuItem value="moderator">Moderator</MenuItem>
<MenuItem value="viewer">Viewer</MenuItem>
</BBBSelect>

<BBBTypography>{LOREM}</BBBTypography>

<BBBAccordion title="Advanced options" ariaLabel="Advanced options">
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<BBBCheckbox label="Enable notifications" ariaLabel="Enable notifications" />
<BBBCheckbox label="Allow data sharing" ariaLabel="Allow data sharing" />
<BBBCheckbox label="Show online status" ariaLabel="Show online status" />
</div>
</BBBAccordion>

<BBBHint label="Changes will take effect on the next session." />

<BBBTypography>{LOREM}</BBBTypography>

<BBBSelect title="Language" value="pt">
<MenuItem value="en">English</MenuItem>
<MenuItem value="pt">Português</MenuItem>
<MenuItem value="es">Español</MenuItem>
</BBBSelect>

<BBBAccordion title="Danger zone" ariaLabel="Danger zone">
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<BBBTypography>{LOREM}</BBBTypography>
<BBButton label="Delete account" variant="primary" ariaLabel="Delete account" onClick={() => {}} />
</div>
</BBBAccordion>

<BBBTypography>{LOREM}</BBBTypography>
</div>
);

export const Default: Story = {
args: {
title: 'Modal Title',
children: (
<div style={{ padding: '1rem' }}>
<BBBTypography>Modal body content</BBBTypography>
</div>
),
isOpen: true,
isOpen: false,
onRequestClose: () => {},
showDividers: false,
allowScroll: true,
noFooter: false,
stickyFooter: true,
shouldCloseOnOverlayClick: true,
shouldCloseOnEsc: true,
children: null,
},
render: (args) => <ModalStory {...args}><ModalBody /></ModalStory>,
};
43 changes: 26 additions & 17 deletions src/components/Modal/component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback } from 'react';
import ReactModal from 'react-modal';
import * as Styled from './styles';
import { BBBTypography } from '../Typography';
Expand Down Expand Up @@ -27,9 +27,25 @@ const Modal: React.FC<ModalProps> = ({
noFooter = false,
footerContent = null,
stickyFooter = true,
testId,
closeButtonDataTest,
children,
...rest
}) => {
let _closeButtonDataTest = closeButtonDataTest;

if (testId && !closeButtonDataTest) {
_closeButtonDataTest = `${testId}-close-button`;
}

const renderFooter = useCallback((isSticky: boolean) => (
<>
{showDividers && <BBBDivider />}
<Styled.ModalFooter $stickyFooter={isSticky}>
{footerContent}
</Styled.ModalFooter>
</>
), [footerContent, showDividers]);

return (
<ReactModal
Expand All @@ -41,6 +57,7 @@ const Modal: React.FC<ModalProps> = ({
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
shouldCloseOnEsc={shouldCloseOnEsc}
appElement={appElement}
testId={testId}
>
<Styled.ModalHeader>
<BBBTypography
Expand All @@ -54,27 +71,19 @@ const Modal: React.FC<ModalProps> = ({
onClick={onRequestClose}
variant="subtle"
ariaLabel="close"
{...(_closeButtonDataTest ? { dataTest: _closeButtonDataTest } : {})}
/>
</Styled.ModalHeader>

{showDividers && <BBBDivider />}

<Styled.ModalBody
$allowScroll={allowScroll}
>
{children}
</Styled.ModalBody>
{(!noFooter || footerContent) && (
<>
{showDividers && (<BBBDivider />)}

<Styled.ModalFooter
$stickyFooter={stickyFooter}
>
{footerContent}
</Styled.ModalFooter>
</>
)}
<Styled.ModalScrollArea $allowScroll={allowScroll}>
<Styled.ModalBodyContent>
{children}
</Styled.ModalBodyContent>
{!noFooter && !stickyFooter && renderFooter(stickyFooter)}
</Styled.ModalScrollArea>
{!noFooter && stickyFooter && renderFooter(stickyFooter)}
</ReactModal>
)
}
Expand Down
Loading
Loading