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
56 changes: 31 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# React Multi Level Table

<div align="center">
<!-- Screenshot of the table component in action -->
<img src="./src/assets/table-image.png" alt="Multi Level Table Component Example" width="800" height="400"/>
</div>

## Table of Contents

- [Overview](#overview)
Expand Down Expand Up @@ -144,33 +149,33 @@ The MultiLevelTable component accepts the following props:
| selectable | boolean | No | false | Enable/disable row selection |

#### State Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| selectionState | SelectionState | Yes | Current selection state with selected rows and all selected flag |
| searchTerm | string | Yes | Current search term for filtering data |
| selectedFilterValues | Set<string \| number> | Yes | Currently selected filter values |
| deletePopup | object | Yes | Delete confirmation popup state (isOpen, itemId, itemName) |
| bulkDeletePopup | object | Yes | Bulk delete confirmation popup state (isOpen, selectedCount) |
| openDropdowns | Set<string> | Yes | Set of currently open dropdown IDs |
| expandedRows | Set<string \| number> | Yes | Set of currently expanded row IDs |
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| selectionState | SelectionState | No | { selectedRows: new Set(), isAllSelected: false } | Current selection state with selected rows and all selected flag |
| searchTerm | string | No | '' | Current search term for filtering data |
| selectedFilterValues | Set<string \| number> | No | new Set() | Currently selected filter values |
| deletePopup | object | No | { isOpen: false, itemId: null, itemName: '' } | Delete confirmation popup state (isOpen, itemId, itemName) |
| bulkDeletePopup | object | No | { isOpen: false, selectedCount: 0 } | Bulk delete confirmation popup state (isOpen, selectedCount) |
| openDropdowns | Set<string> | No | new Set() | Set of currently open dropdown IDs |
| expandedRows | Set<string \| number> | No | new Set() | Set of currently expanded row IDs |

#### Handler Props
| Prop | Type | Description |
|------|------|-------------|
| onSearchChange | (searchTerm: string) => void | Updates the search term for filtering data |
| onFilterChange | (values: Set<string \| number>) => void | Updates the selected filter values |
| onDeleteClick | (itemId: string \| number, itemName: string) => void | Handles delete button click for a specific row |
| onDeleteConfirm | () => void | Confirms the delete action for the selected row |
| onDeleteCancel | () => void | Cancels the delete action and closes popup |
| onBulkDeleteClick | () => void | Handles bulk delete button click for selected rows |
| onBulkDeleteConfirm | () => void | Confirms the bulk delete action for selected rows |
| onBulkDeleteCancel | () => void | Cancels the bulk delete action and closes popup |
| onDropdownToggle | (buttonId: string, isOpen: boolean) => void | Toggles dropdown open/close state for action buttons |
| onDropdownClose | (buttonId: string) => void | Closes a specific dropdown by ID |
| onButtonClick | (button: ButtonConfig) => void | Handles click events for action buttons (export, filter, etc.) |
| onSelectAll | () => void | Handles select all checkbox click to select/deselect all rows |
| onRowSelect | (rowId: string \| number) => void | Handles individual row selection checkbox click |
| onRowToggle | (rowId: string \| number) => void | Handles row expand/collapse toggle for nested rows |
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| onSearchChange | (searchTerm: string) => void | No | - | Updates the search term for filtering data |
| onFilterChange | (values: Set<string \| number>) => void | No | - | Updates the selected filter values |
| onDeleteClick | (itemId: string \| number, itemName: string) => void | No | - | Handles delete button click for a specific row |
| onDeleteConfirm | () => void | No | - | Confirms the delete action for the selected row |
| onDeleteCancel | () => void | No | - | Cancels the delete action and closes popup |
| onBulkDeleteClick | () => void | No | - | Handles bulk delete button click for selected rows |
| onBulkDeleteConfirm | () => void | No | - | Confirms the bulk delete action for selected rows |
| onBulkDeleteCancel | () => void | No | - | Cancels the bulk delete action and closes popup |
| onDropdownToggle | (buttonId: string, isOpen: boolean) => void | No | - | Toggles dropdown open/close state for action buttons |
| onDropdownClose | (buttonId: string) => void | No | - | Closes a specific dropdown by ID |
| onButtonClick | (button: ButtonConfig) => void | No | - | Handles click events for action buttons (export, filter, etc.) |
| onSelectAll | () => void | No | - | Handles select all checkbox click to select/deselect all rows |
| onRowSelect | (rowId: string \| number) => void | No | - | Handles individual row selection checkbox click |
| onRowToggle | (rowId: string \| number) => void | No | - | Handles row expand/collapse toggle for nested rows |

#### Additional Props
| Prop | Type | Default | Description |
Expand Down Expand Up @@ -356,6 +361,7 @@ interface PaginationProps {
previousPage: () => void; // Go to previous page
setPageSize: (pageSize: number) => void; // Change page size
state: TableStateWithPagination<T>; // Current table state
theme?: ThemeProps; // Optional theme for styling
}
```

Expand Down
6 changes: 3 additions & 3 deletions example/src/components/DetailRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ import type { ThemeProps } from '../types/theme';
interface DetailRowProps {
label: string;
value: React.ReactNode;
theme: ThemeProps;
theme?: ThemeProps;
}

export const DetailRow: React.FC<DetailRowProps> = ({ label, value, theme }) => (
<div className="detail-row">
<span
className="detail-row-label"
style={{
color: theme.colors?.textColor || '#666666',
color: theme?.colors?.textColor || '#666666',
}}
>
{label}
</span>
<div
className="detail-row-value"
style={{
color: theme.colors?.textColor || '#333333',
color: theme?.colors?.textColor || '#333333',
}}
>
{value}
Expand Down
6 changes: 3 additions & 3 deletions example/src/components/ExportDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { colors } from '../styles/style';
import '../styles/ExportDropdown.css';

interface ExportDropdownProps {
onClose: () => void;
onClose?: () => void;
handleExportCSV?: () => void;
theme?: {
colors?: {
Expand All @@ -24,7 +24,7 @@ export const ExportDropdown: React.FC<ExportDropdownProps> = ({
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node))
onClose();
onClose?.();
};

document.addEventListener('mousedown', handleClickOutside);
Expand All @@ -36,7 +36,7 @@ export const ExportDropdown: React.FC<ExportDropdownProps> = ({
if (handleExportCSV)
handleExportCSV();

onClose();
onClose?.();
};

return (
Expand Down
48 changes: 24 additions & 24 deletions example/src/components/FilterDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ import '../styles/FilterDropdown.css';

interface FilterDropdownProps {
// State props
tempSelectedValues: Set<string | number>;
selectedCategory: string | null;
categories: Array<{
tempSelectedValues?: Set<string | number>;
selectedCategory?: string | null;
categories?: Array<{
key: string;
title: string;
count: number;
}>;
categoryFilterOptions: FilterOption[];
categoryFilterOptions?: FilterOption[];

// Handler props
onClose: () => void;
onApply: (values: Set<string | number>) => void;
onCancel: () => void;
onReset: () => void;
onCategoryChange: (categoryKey: string) => void;
onSelectAll: () => void;
onOptionChange: (value: string | number) => void;
onClose?: () => void;
onApply?: (values: Set<string | number>) => void;
onCancel?: () => void;
onReset?: () => void;
onCategoryChange?: (categoryKey: string) => void;
onSelectAll?: () => void;
onOptionChange?: (value: string | number) => void;

// Configuration props
title?: string;
Expand All @@ -48,10 +48,10 @@ interface FilterDropdownProps {

export const FilterDropdown: React.FC<FilterDropdownProps> = ({
// State props
tempSelectedValues,
selectedCategory,
categories,
categoryFilterOptions,
tempSelectedValues = new Set(),
selectedCategory = null,
categories = [],
categoryFilterOptions = [],

// Handler props
onClose,
Expand Down Expand Up @@ -80,7 +80,7 @@ export const FilterDropdown: React.FC<FilterDropdownProps> = ({
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node))
onClose();
onClose?.();
};

document.addEventListener('mousedown', handleClickOutside);
Expand All @@ -91,17 +91,17 @@ export const FilterDropdown: React.FC<FilterDropdownProps> = ({
}, [onClose]);

const handleApply = () => {
onApply(tempSelectedValues);
onClose();
onApply?.(tempSelectedValues);
onClose?.();
};

const handleCancel = () => {
onCancel();
onClose();
onCancel?.();
onClose?.();
};

const handleResetFilters = () => {
onReset();
onReset?.();
};

return (
Expand Down Expand Up @@ -169,7 +169,7 @@ export const FilterDropdown: React.FC<FilterDropdownProps> = ({
<div
key={category.key}
className={`category-item ${isSelected ? 'selected' : ''}`}
onClick={() => onCategoryChange(category.key)}
onClick={() => onCategoryChange?.(category.key)}
style={{
cursor: 'pointer',
backgroundColor: isSelected ? '#F3F3FF' : 'transparent',
Expand Down Expand Up @@ -209,7 +209,7 @@ export const FilterDropdown: React.FC<FilterDropdownProps> = ({
ref={(input) => {
if (input) input.indeterminate = isIndeterminate;
}}
onChange={onSelectAll}
onChange={() => onSelectAll?.()}
style={{
borderColor: theme?.table?.filter?.borderColor || '#E5E7EB',
}}
Expand Down Expand Up @@ -238,7 +238,7 @@ export const FilterDropdown: React.FC<FilterDropdownProps> = ({
<input
type="checkbox"
checked={isSelected}
onChange={() => onOptionChange(option.value)}
onChange={() => onOptionChange?.(option.value)}
style={{
borderColor: theme?.table?.filter?.borderColor || '#E5E7EB',
}}
Expand Down
30 changes: 16 additions & 14 deletions example/src/components/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import '../styles/Popup.css';

interface PopupButton {
text: string;
onClick: () => void;
onClick?: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}

interface PopupProps {
isOpen: boolean;
onClose: () => void;
icon: React.ComponentType<{ width?: number; height?: number }>;
onClose?: () => void;
icon?: React.ComponentType<{ width?: number; height?: number }>;
title?: string;
text: string;
buttons: PopupButton[];
text?: string;
buttons?: PopupButton[];
theme?: ThemeProps;
}

Expand All @@ -27,20 +27,20 @@ export const Popup: React.FC<PopupProps> = ({
icon: Icon,
title,
text,
buttons,
buttons = [],
theme,
}) => {
const popupRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (popupRef.current && !popupRef.current.contains(event.target as Node))
onClose();
onClose?.();
};

const handleEscapeKey = (event: KeyboardEvent) => {
if (event.key === 'Escape')
onClose();
onClose?.();
};

if (isOpen) {
Expand All @@ -57,7 +57,7 @@ export const Popup: React.FC<PopupProps> = ({
}, [isOpen, onClose]);

const renderIcon = () => {
return <Icon width={48} height={48} />;
return Icon ? <Icon width={48} height={48} /> : null;
};

if (!isOpen) return null;
Expand All @@ -84,9 +84,11 @@ export const Popup: React.FC<PopupProps> = ({
</div>

<div className="popup-content">
<p className="popup-text" style={{ color: theme?.colors?.textColor || colors.borderDark }}>
{text}
</p>
{text && (
<p className="popup-text" style={{ color: theme?.colors?.textColor || colors.borderDark }}>
{text}
</p>
)}
</div>

<div className="popup-footer">
Expand All @@ -99,11 +101,11 @@ export const Popup: React.FC<PopupProps> = ({
style={{
backgroundColor: button.variant === 'primary'
? (theme?.colors?.primaryColor || colors.primary)
: (theme?.colors?.background || colors.backgroundWhite),
: 'transparent',
color: button.variant === 'primary'
? '#ffffff'
: (theme?.colors?.textColor || colors.borderDark),
borderColor: theme?.colors?.borderColor || colors.borderDark,
borderColor: theme?.colors?.borderColor || colors.borderFilter,
}}
>
{button.text}
Expand Down
Loading
Loading