Skip to content

Commit 0f0d11e

Browse files
committed
feat: add Modal and Spinner
1 parent fbca8e1 commit 0f0d11e

File tree

15 files changed

+444
-0
lines changed

15 files changed

+444
-0
lines changed

package-lock.json

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@babel/preset-typescript": "^7.24.7",
3636
"@babel/register": "^7.24.6",
3737
"@eslint/js": "^9.33.0",
38+
"@types/react-modal": "^3.16.3",
3839
"@types/react-toggle": "^4.0.5",
3940
"@types/styled-components": "^5.1.34",
4041
"@typescript-eslint/eslint-plugin": "^8.39.1",
@@ -73,6 +74,7 @@
7374
"dependencies": {
7475
"@mui/icons-material": "^7.3.1",
7576
"@tippyjs/react": "^4.2.6",
77+
"react-modal": "^3.16.3",
7678
"tippy.js": "^6.3.7"
7779
}
7880
}

src/components/Modal/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# BBBModal
2+
3+
A customizable modal dialog component for React, following the BBB UI design system.
4+
5+
![Example](./assets/example.gif)
6+
7+
## Description
8+
9+
`BBBModal` displays content in a modal overlay, with optional title, dividers, scrollable body, and sticky footer. It supports accessibility features and can be fully customized via props.
10+
11+
12+
## Usage Example
13+
14+
```tsx
15+
import { BBBModal } from 'bbb-ui-components-react';
16+
17+
<BBBModal
18+
isOpen={isOpen}
19+
onRequestClose={handleClose}
20+
title="Example Modal"
21+
showDividers
22+
footerContent={<button onClick={handleClose}>Close</button>}
23+
>
24+
<p>This is the modal content.</p>
25+
</BBBModal>
26+
```
27+
28+
## Props
29+
30+
| Prop | Type | Default | Description |
31+
|-----------------------------|--------------------------------|-----------|-----------------------------------------------------------------------------|
32+
| `isOpen` | `boolean` | `true` | Controls whether the modal is open. |
33+
| `onRequestClose` | `() => void` || Function called when requesting to close the modal. |
34+
| `appElement` | `HTMLElement \| string` || App element for accessibility. |
35+
| `title` | `string` || Modal title. |
36+
| `contentLabel` | `string` || Accessibility label for modal content. |
37+
| `showDividers` | `boolean` | `false` | Shows dividers between header, body, and footer. |
38+
| `shouldCloseOnOverlayClick` | `boolean` | `false` | Allows closing when clicking outside the modal. |
39+
| `shouldCloseOnEsc` | `boolean` | `false` | Allows closing with ESC key. |
40+
| `allowScroll` | `boolean` | `true` | Enables scroll in the modal body. |
41+
| `noFooter` | `boolean` | `false` | Hides the modal footer. |
42+
| `footerContent` | `React.ReactNode` | `null` | Custom content for the footer. |
43+
| `stickyFooter` | `boolean` | `true` | Makes the footer sticky. |
44+
| `children` | `React.ReactNode` || Modal content. |
45+
46+
See [`ModalProps`](./types.ts) for full type definitions.
47+
48+
## Accessibility
49+
- The `appElement` and `contentLabel` props help ensure accessibility compliance.
50+
- The modal can be closed with ESC or by clicking outside, if enabled.
51+
52+
## Customization
53+
- Use the `footerContent` prop to add custom actions or information to the modal footer.
54+
- The `stickyFooter` prop keeps the footer visible when scrolling.
55+
56+
---
57+
58+
For more details, see the [types file](./types.ts) and the [component implementation](./component.tsx).
125 KB
Loading

src/components/Modal/component.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React from 'react';
2+
import ReactModal from 'react-modal';
3+
import * as Styled from './styles';
4+
import { BBBTypography } from '../Typography';
5+
import { MdClose } from 'react-icons/md';
6+
import { BBBDivider } from '../Divider';
7+
import { ModalProps } from './types';
8+
import { BBButton } from '../..';
9+
10+
/**
11+
* BBBModal component
12+
*
13+
* Displays a customizable modal with optional title, body, and footer.
14+
* Supports accessibility, dividers, scrollable body, and sticky footer.
15+
*
16+
* @param {ModalProps} props The props for the BBBModal component. See {@link ModalProps} for more details.
17+
* @param {boolean} isOpen - Controls whether the modal is open
18+
* @param {() => void} onRequestClose - Function called when requesting to close the modal
19+
* @param {HTMLElement | string} [appElement] - App element for accessibility
20+
* @param {string} [title] - Modal title
21+
* @param {string} [contentLabel] - Accessibility label for modal content
22+
* @param {boolean} [showDividers] - Shows dividers between header, body, and footer
23+
* @param {boolean} [shouldCloseOnOverlayClick] - Allows closing when clicking outside the modal
24+
* @param {boolean} [shouldCloseOnEsc] - Allows closing with ESC key
25+
* @param {boolean} [allowScroll] - Enables scroll in the modal body
26+
* @param {boolean} [noFooter] - Hides the modal footer
27+
* @param {React.ReactNode} [footerContent] - Custom content for the footer
28+
* @param {boolean} [stickyFooter] - Makes the footer sticky
29+
* @param {React.ReactNode} children - Modal content
30+
*/
31+
const Modal: React.FC<ModalProps> = ({
32+
isOpen = true,
33+
onRequestClose,
34+
appElement,
35+
title,
36+
contentLabel,
37+
showDividers = false,
38+
shouldCloseOnOverlayClick = false,
39+
shouldCloseOnEsc = false,
40+
allowScroll = true,
41+
noFooter = false,
42+
footerContent = null,
43+
stickyFooter = true,
44+
children,
45+
}) => {
46+
47+
return (
48+
<ReactModal
49+
isOpen={isOpen}
50+
onRequestClose={onRequestClose}
51+
contentLabel={contentLabel}
52+
style={Styled.modalStyles}
53+
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
54+
shouldCloseOnEsc={shouldCloseOnEsc}
55+
appElement={appElement}
56+
>
57+
<Styled.ModalHeader>
58+
<BBBTypography
59+
variant="header"
60+
>
61+
{title}
62+
</BBBTypography>
63+
<BBButton
64+
layout="circle"
65+
icon={<MdClose size="1.5rem" />}
66+
onClick={onRequestClose}
67+
variant="subtle"
68+
ariaLabel="close"
69+
/>
70+
</Styled.ModalHeader>
71+
72+
{showDividers && <BBBDivider />}
73+
74+
<Styled.ModalBody
75+
allowScroll={allowScroll}
76+
>
77+
{children}
78+
</Styled.ModalBody>
79+
{(!noFooter || footerContent) && (
80+
<>
81+
{showDividers && (<BBBDivider />)}
82+
83+
<Styled.ModalFooter
84+
stickyFooter={stickyFooter}
85+
>
86+
{footerContent}
87+
</Styled.ModalFooter>
88+
</>
89+
)}
90+
</ReactModal>
91+
)
92+
}
93+
94+
export default Modal;

src/components/Modal/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as BBBModal } from './component';

src/components/Modal/styles.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import styled from 'styled-components';
2+
import { Styles } from 'react-modal';
3+
import * as React from 'react';
4+
import { spacingLarge, spacingMedium, spacingSmallMedium, borderRadiusDefault } from '../../stylesheets/sizing';
5+
import { StyledModalBodyProps, StyledModalFooterProps } from './types';
6+
7+
export const modalStyles: Styles = {
8+
overlay: {
9+
position: 'fixed',
10+
top: 0,
11+
left: 0,
12+
right: 0,
13+
bottom: 0,
14+
backgroundColor: 'rgba(0, 0, 0, 0.75)',
15+
zIndex: 100,
16+
display: 'flex',
17+
alignItems: 'center',
18+
justifyContent: 'center',
19+
},
20+
content: {
21+
position: 'relative',
22+
top: 'auto',
23+
left: 'auto',
24+
right: 'auto',
25+
bottom: 'auto',
26+
borderRadius: borderRadiusDefault,
27+
background: '#fff',
28+
overflow: 'hidden',
29+
WebkitOverflowScrolling: 'touch',
30+
outline: 'none',
31+
padding: '0px',
32+
minWidth: '300px',
33+
maxWidth: '90vw',
34+
maxHeight: '90vh',
35+
display: 'flex',
36+
flexDirection: 'column' as React.CSSProperties['flexDirection'],
37+
},
38+
};
39+
40+
export const ModalHeader = styled.div`
41+
padding: ${spacingLarge} ${spacingLarge} ${spacingSmallMedium};
42+
display: flex;
43+
justify-content: space-between;
44+
align-items: center;
45+
`;
46+
47+
export const CloseButton = styled.button`
48+
background: none;
49+
border: none;
50+
font-size: 1.5rem;
51+
cursor: pointer;
52+
padding: 0;
53+
line-height: 1;
54+
`;
55+
56+
export const ModalBody = styled.div<StyledModalBodyProps>`
57+
flex-grow: 1;
58+
overflow-y: ${({ allowScroll }) => allowScroll ? 'auto' : 'hidden'};
59+
overflow-x: hidden;
60+
display: flex;
61+
flex-direction: column;
62+
padding: 0 ${spacingLarge} 0;
63+
margin: ${spacingSmallMedium} 0 ${spacingSmallMedium};
64+
`;
65+
66+
export const ModalFooter = styled.div<StyledModalFooterProps>`
67+
padding: ${spacingSmallMedium} ${spacingLarge} ${spacingLarge};
68+
display: flex;
69+
justify-content: flex-end;
70+
gap: ${spacingMedium};
71+
`;

src/components/Modal/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
3+
export interface StyledModalBodyProps {
4+
allowScroll: boolean;
5+
}
6+
7+
export interface StyledModalFooterProps {
8+
stickyFooter: boolean;
9+
}
10+
11+
export interface ModalProps {
12+
isOpen?: boolean;
13+
onRequestClose: () => void;
14+
appElement?: HTMLElement | HTMLElement[] | HTMLCollection | NodeList | undefined;
15+
title?: string;
16+
contentLabel?: string;
17+
showDividers?: boolean;
18+
shouldCloseOnOverlayClick?: boolean;
19+
shouldCloseOnEsc?: boolean;
20+
allowScroll?: boolean;
21+
noFooter?: boolean;
22+
stickyFooter?: boolean;
23+
footerContent?: React.ReactNode;
24+
children: React.ReactNode;
25+
}

src/components/Spinner/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# BBBSpinner
2+
3+
A loading spinner component for indicating background activity or loading states.
4+
5+
![Demo](assets/example.gif)
6+
7+
## Usage Example
8+
9+
```jsx
10+
import { BBBSpinner } from 'bbb-ui-components-react';
11+
12+
<BBBSpinner />
13+
<BBBSpinner size={32} />
14+
<BBBSpinner size="2rem" />
15+
<BBBSpinner strokeWidth={2} />
16+
<BBBSpinner animate={false} />
17+
```
18+
19+
## Props
20+
21+
| Prop | Type | Default | Description |
22+
|--------------|------------------|---------|----------------------------------------------------------|
23+
| `size` | number \| string | 48 | The width and height of the spinner (px or CSS string). |
24+
| `strokeWidth`| number | 4 | The thickness of the spinner stroke. |
25+
| `animate` | boolean | true | If false, disables the animation. |
26+
27+
## Accessibility
28+
29+
- Uses `role="progressbar"` and `aria-busy` for accessibility.
30+
- The spinner is hidden from assistive tech when `animate`.
31+
58.8 KB
Loading

0 commit comments

Comments
 (0)