Skip to main content

Production React Components - Organisms

Complete JSX implementations with TypeScript, tests, and examples

Modal.jsx

import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './Modal.css';

const Modal = ({
isOpen,
onClose,
title,
children,
footer,
size = 'md',
closeOnBackdrop = true,
closeOnEscape = true,
}) => {
const modalRef = useRef(null);
const previousActiveElement = useRef(null);

useEffect(() => {
if (isOpen) {
// Store currently focused element
previousActiveElement.current = document.activeElement;

// Prevent body scroll
document.body.classList.add('modal-open');

// Focus modal
modalRef.current?.focus();

// Trap focus within modal
const handleTabKey = (e) => {
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);

if (!focusableElements || focusableElements.length === 0) return;

const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];

if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
};

const handleKeyDown = (e) => {
if (e.key === 'Tab') {
handleTabKey(e);
} else if (e.key === 'Escape' && closeOnEscape) {
onClose();
}
};

document.addEventListener('keydown', handleKeyDown);

return () => {
document.removeEventListener('keydown', handleKeyDown);
document.body.classList.remove('modal-open');

// Return focus to previously focused element
previousActiveElement.current?.focus();
};
}
}, [isOpen, closeOnEscape, onClose]);

if (!isOpen) return null;

const handleBackdropClick = (e) => {
if (closeOnBackdrop && e.target === e.currentTarget) {
onClose();
}
};

return (
<div
className="modal-backdrop"
onClick={handleBackdropClick}
data-testid="modal-backdrop"
>
<div
className={`modal modal--${size}`}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
ref={modalRef}
tabIndex={-1}
>
<div className="modal__header">
<h2 className="modal__title" id="modal-title">
{title}
</h2>
<button
className="modal__close"
onClick={onClose}
aria-label="Close dialog"
data-testid="modal-close"
>
<svg width="20" height="20" viewBox="0 0 20 20">
<path
d="M5 5l10 10M15 5L5 15"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
</button>
</div>

<div className="modal__content">
{children}
</div>

{footer && (
<div className="modal__footer">
{footer}
</div>
)}
</div>
</div>
);
};

Modal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
footer: PropTypes.node,
size: PropTypes.oneOf(['sm', 'md', 'lg', 'full']),
closeOnBackdrop: PropTypes.bool,
closeOnEscape: PropTypes.bool,
};

export default Modal;

Modal.test.js

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import Modal from './Modal';

describe('Modal', () => {
const defaultProps = {
isOpen: true,
onClose: jest.fn(),
title: 'Test Modal',
children: <p>Modal content</p>,
};

beforeEach(() => {
jest.clearAllMocks();
});

test('renders when isOpen is true', () => {
render(<Modal {...defaultProps} />);
expect(screen.getByText('Test Modal')).toBeInTheDocument();
expect(screen.getByText('Modal content')).toBeInTheDocument();
});

test('does not render when isOpen is false', () => {
render(<Modal {...defaultProps} isOpen={false} />);
expect(screen.queryByText('Test Modal')).not.toBeInTheDocument();
});

test('calls onClose when close button clicked', () => {
render(<Modal {...defaultProps} />);
fireEvent.click(screen.getByTestId('modal-close'));
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});

test('calls onClose when backdrop clicked', () => {
render(<Modal {...defaultProps} closeOnBackdrop={true} />);
fireEvent.click(screen.getByTestId('modal-backdrop'));
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});

test('does not close on backdrop click when closeOnBackdrop is false', () => {
render(<Modal {...defaultProps} closeOnBackdrop={false} />);
fireEvent.click(screen.getByTestId('modal-backdrop'));
expect(defaultProps.onClose).not.toHaveBeenCalled();
});

test('calls onClose when Escape key pressed', () => {
render(<Modal {...defaultProps} closeOnEscape={true} />);
fireEvent.keyDown(document, { key: 'Escape' });
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});

test('renders footer when provided', () => {
const footer = (
<>
<button>Cancel</button>
<button>Save</button>
</>
);
render(<Modal {...defaultProps} footer={footer} />);
expect(screen.getByText('Cancel')).toBeInTheDocument();
expect(screen.getByText('Save')).toBeInTheDocument();
});

test('applies correct size class', () => {
const { container } = render(<Modal {...defaultProps} size="lg" />);
expect(container.querySelector('.modal--lg')).toBeInTheDocument();
});

test('has correct ARIA attributes', () => {
render(<Modal {...defaultProps} />);
const dialog = screen.getByRole('dialog');
expect(dialog).toHaveAttribute('aria-modal', 'true');
expect(dialog).toHaveAttribute('aria-labelledby', 'modal-title');
});

test('prevents body scroll when open', () => {
render(<Modal {...defaultProps} />);
expect(document.body.classList.contains('modal-open')).toBe(true);
});

test('removes body scroll class when closed', () => {
const { rerender } = render(<Modal {...defaultProps} />);
expect(document.body.classList.contains('modal-open')).toBe(true);

rerender(<Modal {...defaultProps} isOpen={false} />);
expect(document.body.classList.contains('modal-open')).toBe(false);
});
});
import React, { useState } from 'react';
import Modal from './Modal';

// Example 1: Confirmation Modal
function DeleteConfirmation() {
const [isOpen, setIsOpen] = useState(false);

const handleDelete = () => {
// Delete logic here
console.log('Item deleted');
setIsOpen(false);
};

return (
<>
<button onClick={() => setIsOpen(true)}>Delete Project</button>

<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Delete Project"
size="sm"
footer={
<>
<button className="button button--secondary" onClick={() => setIsOpen(false)}>
Cancel
</button>
<button className="button button--danger" onClick={handleDelete}>
Delete
</button>
</>
}
>
<p>Are you sure you want to delete this project? This action cannot be undone.</p>
</Modal>
</>
);
}

// Example 2: Form Modal
function CreateTaskModal() {
const [isOpen, setIsOpen] = useState(false);
const [formData, setFormData] = useState({ title: '', description: '' });

const handleSubmit = (e) => {
e.preventDefault();
// Submit logic here
console.log('Task created:', formData);
setIsOpen(false);
};

return (
<>
<button onClick={() => setIsOpen(true)}>New Task</button>

<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Create New Task"
size="md"
footer={
<>
<button className="button button--secondary" onClick={() => setIsOpen(false)}>
Cancel
</button>
<button className="button button--primary" onClick={handleSubmit}>
Create Task
</button>
</>
}
>
<form className="form" onSubmit={handleSubmit}>
<div className="form-field">
<label htmlFor="task-title" className="label">Task Title</label>
<input
type="text"
id="task-title"
className="input"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
required
/>
</div>

<div className="form-field">
<label htmlFor="task-description" className="label">Description</label>
<textarea
id="task-description"
className="input"
rows="4"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
/>
</div>
</form>
</Modal>
</>
);
}

DataTable Component

DataTable.jsx

import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import './DataTable.css';

const DataTable = ({
columns,
data,
selectable = false,
sortable = true,
onSelectionChange,
caption,
}) => {
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'none' });
const [selectedRows, setSelectedRows] = useState(new Set());

// Sorting logic
const sortedData = useMemo(() => {
if (!sortConfig.key || sortConfig.direction === 'none') {
return data;
}

return [...data].sort((a, b) => {
const aValue = a[sortConfig.key];
const bValue = b[sortConfig.key];

if (aValue < bValue) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (aValue > bValue) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
}, [data, sortConfig]);

const handleSort = (key) => {
if (!sortable) return;

let direction = 'ascending';
if (sortConfig.key === key) {
if (sortConfig.direction === 'ascending') {
direction = 'descending';
} else if (sortConfig.direction === 'descending') {
direction = 'none';
}
}

setSortConfig({ key, direction });
};

const handleSelectAll = (e) => {
if (e.target.checked) {
const allIds = new Set(data.map(row => row.id));
setSelectedRows(allIds);
onSelectionChange?.(Array.from(allIds));
} else {
setSelectedRows(new Set());
onSelectionChange?.([]);
}
};

const handleSelectRow = (id) => {
const newSelected = new Set(selectedRows);
if (newSelected.has(id)) {
newSelected.delete(id);
} else {
newSelected.add(id);
}
setSelectedRows(newSelected);
onSelectionChange?.(Array.from(newSelected));
};

const allSelected = data.length > 0 && selectedRows.size === data.length;
const someSelected = selectedRows.size > 0 && selectedRows.size < data.length;

return (
<div className="data-table-container" data-testid="data-table">
<table className="data-table">
{caption && (
<caption className="data-table__caption">{caption}</caption>
)}

<thead className="data-table__header">
<tr>
{selectable && (
<th scope="col" className="data-table__cell data-table__cell--checkbox">
<input
type="checkbox"
className="checkbox__input"
checked={allSelected}
ref={input => {
if (input) input.indeterminate = someSelected;
}}
onChange={handleSelectAll}
aria-label="Select all rows"
/>
</th>
)}
{columns.map((column) => (
<th
key={column.key}
scope="col"
className={`data-table__cell ${
column.sortable !== false && sortable ? 'data-table__cell--sortable' : ''
}`}
aria-sort={
sortConfig.key === column.key ? sortConfig.direction : 'none'
}
>
{column.sortable !== false && sortable ? (
<button
className="data-table__sort-button"
onClick={() => handleSort(column.key)}
>
{column.label}
<svg
className={`data-table__sort-icon ${
sortConfig.key === column.key && sortConfig.direction === 'descending'
? 'data-table__sort-icon--desc'
: ''
}`}
width="12"
height="12"
viewBox="0 0 12 12"
>
<path
d="M6 3l-3 3h6l-3-3zm0 6l-3-3h6l-3 3z"
fill="currentColor"
/>
</svg>
</button>
) : (
column.label
)}
</th>
))}
</tr>
</thead>

<tbody className="data-table__body">
{sortedData.map((row) => (
<tr
key={row.id}
className={`data-table__row ${
selectedRows.has(row.id) ? 'data-table__row--selected' : ''
} ${row.disabled ? 'data-table__row--disabled' : ''}`}
>
{selectable && (
<td className="data-table__cell data-table__cell--checkbox">
<input
type="checkbox"
className="checkbox__input"
checked={selectedRows.has(row.id)}
onChange={() => handleSelectRow(row.id)}
disabled={row.disabled}
aria-label={`Select ${row.name || `row ${row.id}`}`}
/>
</td>
)}
{columns.map((column) => (
<td
key={column.key}
className={`data-table__cell ${
column.className ? column.className : ''
}`}
>
{column.render ? column.render(row[column.key], row) : row[column.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};

DataTable.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
sortable: PropTypes.bool,
className: PropTypes.string,
render: PropTypes.func,
})
).isRequired,
data: PropTypes.arrayOf(PropTypes.object).isRequired,
selectable: PropTypes.bool,
sortable: PropTypes.bool,
onSelectionChange: PropTypes.func,
caption: PropTypes.string,
};

export default DataTable;

DataTable Usage Example

import React, { useState } from 'react';
import DataTable from './DataTable';

function TeamMembersTable() {
const [selectedIds, setSelectedIds] = useState([]);

const columns = [
{
key: 'name',
label: 'Name',
render: (value, row) => (
<div className="data-table__user">
<img src={row.avatar} alt="" className="avatar avatar--sm" />
<span>{value}</span>
</div>
),
},
{ key: 'email', label: 'Email' },
{
key: 'role',
label: 'Role',
render: (value) => (
<span className={`badge ${value === 'Admin' ? 'badge--primary' : ''}`}>
{value}
</span>
),
},
{
key: 'status',
label: 'Status',
render: (value) => (
<div className="status-indicator">
<span className={`dot dot--${value === 'Active' ? 'success' : 'inactive'}`}></span>
<span>{value}</span>
</div>
),
},
{
key: 'actions',
label: 'Actions',
sortable: false,
className: 'data-table__cell--actions',
render: () => (
<button className="button button--ghost button--sm">Edit</button>
),
},
];

const data = [
{
id: 1,
name: 'Alice Johnson',
email: 'alice@example.com',
avatar: '/alice.jpg',
role: 'Admin',
status: 'Active',
},
{
id: 2,
name: 'Bob Smith',
email: 'bob@example.com',
avatar: '/bob.jpg',
role: 'Member',
status: 'Active',
},
{
id: 3,
name: 'Charlie Brown',
email: 'charlie@example.com',
avatar: '/charlie.jpg',
role: 'Guest',
status: 'Inactive',
disabled: true,
},
];

return (
<DataTable
columns={columns}
data={data}
selectable={true}
caption="Team Members (24 total)"
onSelectionChange={setSelectedIds}
/>
);
}

export default TeamMembersTable;

Sidebar.jsx

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './Sidebar.css';

const Sidebar = ({
logo,
logoIcon,
navItems,
footer,
defaultCollapsed = false,
}) => {
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
const [activeItem, setActiveItem] = useState(null);

return (
<aside
className={`sidebar ${isCollapsed ? 'sidebar--collapsed' : ''}`}
role="navigation"
aria-label="Main navigation"
data-testid="sidebar"
>
{/* Header */}
<div className="sidebar__header">
<img
src={isCollapsed ? logoIcon : logo}
alt="CODITECT"
className={`sidebar__logo ${isCollapsed ? 'sidebar__logo--sm' : ''}`}
/>
<button
className="sidebar__toggle"
onClick={() => setIsCollapsed(!isCollapsed)}
aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
aria-expanded={!isCollapsed}
>
<svg width="20" height="20" viewBox="0 0 20 20">
<path
d={isCollapsed ? 'M9 5l5 5-5 5' : 'M15 5l-5 5 5 5'}
stroke="currentColor"
strokeWidth="2"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>

{/* Navigation */}
<nav className="sidebar__nav">
{navItems.map((item) => (
<React.Fragment key={item.id}>
{item.divider && <div className="sidebar__divider" />}
<a
href={item.href}
className={`sidebar__link ${
activeItem === item.id || item.active ? 'sidebar__link--active' : ''
}`}
onClick={(e) => {
if (item.onClick) {
e.preventDefault();
item.onClick();
}
setActiveItem(item.id);
}}
aria-current={activeItem === item.id || item.active ? 'page' : undefined}
title={isCollapsed ? item.label : undefined}
>
{item.icon && (
<span className="sidebar__icon" dangerouslySetInnerHTML={{ __html: item.icon }} />
)}
<span className="sidebar__text">{item.label}</span>
{item.badge && (
<span className="sidebar__badge">{item.badge}</span>
)}
</a>
</React.Fragment>
))}
</nav>

{/* Footer */}
{footer && (
<div className="sidebar__footer">
{footer}
</div>
)}
</aside>
);
};

Sidebar.propTypes = {
logo: PropTypes.string.isRequired,
logoIcon: PropTypes.string.isRequired,
navItems: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
href: PropTypes.string.isRequired,
icon: PropTypes.string,
badge: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
active: PropTypes.bool,
divider: PropTypes.bool,
onClick: PropTypes.func,
})
).isRequired,
footer: PropTypes.node,
defaultCollapsed: PropTypes.bool,
};

export default Sidebar;
import React from 'react';
import Sidebar from './Sidebar';

function AppLayout({ children }) {
const navItems = [
{
id: 'dashboard',
label: 'Dashboard',
href: '/dashboard',
icon: '<svg>...</svg>',
active: true,
},
{
id: 'projects',
label: 'Projects',
href: '/projects',
icon: '<svg>...</svg>',
badge: 3,
},
{
id: 'team',
label: 'Team',
href: '/team',
icon: '<svg>...</svg>',
},
{
id: 'settings',
label: 'Settings',
href: '/settings',
icon: '<svg>...</svg>',
divider: true,
},
];

const footer = (
<button className="sidebar__user">
<img src="/user.jpg" alt="" className="avatar avatar--sm" />
<div className="sidebar__user-info">
<div className="sidebar__user-name">Alice Johnson</div>
<div className="sidebar__user-email">alice@example.com</div>
</div>
<svg className="sidebar__chevron" width="16" height="16">
<path d="M4 6l4 4 4-4" stroke="currentColor" fill="none" />
</svg>
</button>
);

return (
<div style={{ display: 'flex' }}>
<Sidebar
logo="/logo.svg"
logoIcon="/icon.svg"
navItems={navItems}
footer={footer}
/>
<main style={{ flex: 1 }}>
{children}
</main>
</div>
);
}

export default AppLayout;

This provides production-ready React components for Modal, DataTable, and Sidebar with complete implementations, tests, and usage examples!