@reactorui/datagrid 1.0.24 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"FilterControls.d.ts","sourceRoot":"","sources":["../../../src/components/Filter/FilterControls.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAMnE,UAAU,mBAAmB,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA6FD,eAAO,MAAM,cAAc,GAAI,CAAC,EAAG,qGAQhC,mBAAmB,CAAC,CAAC,CAAC,4CA2RxB,CAAC"}
1
+ {"version":3,"file":"FilterControls.d.ts","sourceRoot":"","sources":["../../../src/components/Filter/FilterControls.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAMnE,UAAU,mBAAmB,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA2FD,eAAO,MAAM,cAAc,GAAI,CAAC,EAAG,qGAQhC,mBAAmB,CAAC,CAAC,CAAC,4CAkUxB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // File: src/components/Filter/FilterControls.tsx
3
3
  import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
4
+ import { createPortal } from 'react-dom';
4
5
  // ============================================================================
5
6
  // Operator Configuration
6
7
  // ============================================================================
@@ -41,14 +42,13 @@ const DEFAULT_OPERATORS = [{ value: 'eq', label: 'equals' }];
41
42
  // Styles
42
43
  // ============================================================================
43
44
  const styles = {
44
- select: 'w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed',
45
- input: 'w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:cursor-not-allowed',
46
- inputDisabled: 'w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed',
47
- buttonPrimary: 'w-full px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white text-sm rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-300 dark:disabled:bg-gray-600 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed transition-colors duration-150',
48
- buttonSecondary: 'w-full px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-sm rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 dark:focus:ring-gray-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150',
49
- filterTag: 'flex items-center justify-between gap-2 px-3 py-2 bg-blue-50 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200 rounded-md text-sm',
50
- filterTagRemove: 'text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-100 focus:outline-none transition-colors duration-150',
51
- label: 'block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1',
45
+ select: 'w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed',
46
+ input: 'w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:cursor-not-allowed',
47
+ inputDisabled: 'w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed',
48
+ buttonPrimary: 'w-full px-4 py-2.5 bg-blue-600 dark:bg-blue-700 text-white text-sm font-medium rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:bg-gray-300 dark:disabled:bg-gray-600 disabled:text-gray-500 dark:disabled:text-gray-400 disabled:cursor-not-allowed transition-colors duration-150',
49
+ label: 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5',
50
+ filterTag: 'inline-flex items-center gap-1.5 px-2.5 py-1 bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200 rounded-md text-xs font-medium whitespace-nowrap',
51
+ filterTagRemove: 'ml-0.5 text-blue-600 dark:text-blue-300 hover:text-blue-800 dark:hover:text-blue-100 focus:outline-none',
52
52
  };
53
53
  // ============================================================================
54
54
  // Filter Icon
@@ -62,10 +62,40 @@ export const FilterControls = ({ columns, activeFilters, onApplyFilter, onRemove
62
62
  const [filterColumn, setFilterColumn] = useState('');
63
63
  const [filterOperator, setFilterOperator] = useState('eq');
64
64
  const [filterValue, setFilterValue] = useState('');
65
- const popoverRef = useRef(null);
65
+ const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0 });
66
66
  const buttonRef = useRef(null);
67
+ const popoverRef = useRef(null);
67
68
  const isDisabled = disabled || filterLoading;
68
69
  const filterCount = activeFilters.length;
70
+ // Calculate popover position relative to viewport with edge detection
71
+ const updatePopoverPosition = useCallback(() => {
72
+ if (buttonRef.current) {
73
+ const rect = buttonRef.current.getBoundingClientRect();
74
+ const popoverWidth = 320; // w-80
75
+ const viewportWidth = window.innerWidth;
76
+ // Adjust left position if popover would overflow right edge
77
+ let left = rect.left;
78
+ if (left + popoverWidth > viewportWidth - 16) {
79
+ left = Math.max(16, viewportWidth - popoverWidth - 16);
80
+ }
81
+ setPopoverPosition({
82
+ top: rect.bottom + 8,
83
+ left,
84
+ });
85
+ }
86
+ }, []);
87
+ // Update position when opening
88
+ useEffect(() => {
89
+ if (isOpen) {
90
+ updatePopoverPosition();
91
+ window.addEventListener('scroll', updatePopoverPosition, true);
92
+ window.addEventListener('resize', updatePopoverPosition);
93
+ return () => {
94
+ window.removeEventListener('scroll', updatePopoverPosition, true);
95
+ window.removeEventListener('resize', updatePopoverPosition);
96
+ };
97
+ }
98
+ }, [isOpen, updatePopoverPosition]);
69
99
  // Close on outside click
70
100
  useEffect(() => {
71
101
  const handleClickOutside = (event) => {
@@ -126,12 +156,6 @@ export const FilterControls = ({ columns, activeFilters, onApplyFilter, onRemove
126
156
  onApplyFilter,
127
157
  isDisabled,
128
158
  ]);
129
- const handleClear = useCallback(() => {
130
- setFilterColumn('');
131
- setFilterOperator('eq');
132
- setFilterValue('');
133
- onClearFilters();
134
- }, [onClearFilters]);
135
159
  const handleKeyDown = useCallback((e) => {
136
160
  if (e.key === 'Enter' && canApply && !isDisabled) {
137
161
  handleApply();
@@ -162,7 +186,13 @@ export const FilterControls = ({ columns, activeFilters, onApplyFilter, onRemove
162
186
  return _jsx("input", { ...commonProps, type: "text", placeholder: "Enter value" });
163
187
  }
164
188
  };
165
- return (_jsxs("div", { className: "relative inline-block", children: [_jsxs("button", { ref: buttonRef, onClick: () => setIsOpen(!isOpen), disabled: isDisabled, title: filterCount > 0
189
+ return (_jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [_jsxs("button", { ref: buttonRef, onClick: () => setIsOpen(!isOpen), disabled: isDisabled, title: filterCount > 0
166
190
  ? `${filterCount} filter${filterCount === 1 ? '' : 's'} active`
167
- : 'Add filter', className: "relative p-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: [_jsx(FilterIcon, { className: "w-5 h-5" }), filterCount > 0 && (_jsx("span", { className: "absolute -top-1 -right-1 min-w-[18px] h-[18px] flex items-center justify-center px-1 text-xs font-medium text-white bg-blue-600 dark:bg-blue-500 rounded-full", children: filterCount }))] }), isOpen && (_jsx("div", { ref: popoverRef, className: "absolute top-full left-0 mt-2 w-72 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-50", children: _jsxs("div", { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Filter" }), _jsx("button", { onClick: () => setIsOpen(false), className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsxs("div", { children: [_jsx("label", { className: styles.label, children: "Column" }), _jsxs("select", { value: filterColumn, onChange: (e) => handleColumnChange(e.target.value), disabled: isDisabled, className: styles.select, children: [_jsx("option", { value: "", children: "Select column" }), filterableColumns.map((col) => (_jsx("option", { value: String(col.key), children: col.label }, String(col.key))))] })] }), _jsxs("div", { children: [_jsx("label", { className: styles.label, children: "Operator" }), _jsx("select", { value: filterOperator, onChange: (e) => setFilterOperator(e.target.value), disabled: isDisabled || !filterColumn, className: styles.select, children: operatorOptions.map((op) => (_jsx("option", { value: op.value, children: op.label }, op.value))) })] }), _jsxs("div", { children: [_jsx("label", { className: styles.label, children: "Value" }), renderValueInput()] }), _jsx("button", { onClick: handleApply, disabled: isDisabled || !canApply, className: styles.buttonPrimary, children: "Apply Filter" }), activeFilters.length > 0 && (_jsxs("div", { className: "border-t border-gray-200 dark:border-gray-700 pt-4 space-y-2", children: [_jsx("div", { className: "text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide", children: "Active Filters" }), _jsx("div", { className: "space-y-2 max-h-32 overflow-y-auto", children: activeFilters.map((filter, index) => (_jsxs("div", { className: styles.filterTag, children: [_jsx("span", { className: "truncate", children: filter.label }), _jsx("button", { onClick: () => onRemoveFilter(index), disabled: isDisabled, className: styles.filterTagRemove, "aria-label": `Remove filter: ${filter.label}`, children: "\u00D7" })] }, `${filter.column}-${index}`))) }), _jsx("button", { onClick: handleClear, disabled: isDisabled, className: styles.buttonSecondary, children: "Clear All" })] }))] }) }))] }));
191
+ : 'Add filter', className: "relative flex-shrink-0 p-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-150", children: [_jsx(FilterIcon, { className: "w-5 h-5" }), filterCount > 0 && (_jsx("span", { className: "absolute -top-1 -right-1 min-w-[18px] h-[18px] flex items-center justify-center px-1 text-xs font-medium text-white bg-blue-600 dark:bg-blue-500 rounded-full", children: filterCount }))] }), activeFilters.length > 0 && (_jsx("div", { className: "flex-1 min-w-0 overflow-x-auto", style: { scrollbarWidth: 'none', msOverflowStyle: 'none' }, children: _jsxs("div", { className: "flex items-center gap-1.5 py-0.5", children: [activeFilters.map((filter, index) => (_jsxs("span", { className: styles.filterTag, children: [filter.label, _jsx("button", { onClick: () => onRemoveFilter(index), disabled: isDisabled, className: styles.filterTagRemove, "aria-label": `Remove filter: ${filter.label}`, children: "\u00D7" })] }, `${filter.column}-${index}`))), activeFilters.length > 1 && (_jsx("button", { onClick: onClearFilters, disabled: isDisabled, className: "flex-shrink-0 text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 whitespace-nowrap px-2 py-1 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors", children: "Clear all" }))] }) })), isOpen &&
192
+ createPortal(_jsx("div", { ref: popoverRef, className: "fixed w-80 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700", style: {
193
+ top: popoverPosition.top,
194
+ padding: 14,
195
+ left: popoverPosition.left,
196
+ zIndex: 99999,
197
+ }, children: _jsxs("div", { className: "p-5 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: "Add Filter" }), _jsx("button", { onClick: () => setIsOpen(false), className: "p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors", children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsxs("div", { children: [_jsx("label", { className: styles.label, children: "Column" }), _jsxs("select", { value: filterColumn, onChange: (e) => handleColumnChange(e.target.value), disabled: isDisabled, className: styles.select, children: [_jsx("option", { value: "", children: "Select column" }), filterableColumns.map((col) => (_jsx("option", { value: String(col.key), children: col.label }, String(col.key))))] })] }), _jsxs("div", { children: [_jsx("label", { className: styles.label, children: "Operator" }), _jsx("select", { value: filterOperator, onChange: (e) => setFilterOperator(e.target.value), disabled: isDisabled || !filterColumn, className: styles.select, children: operatorOptions.map((op) => (_jsx("option", { value: op.value, children: op.label }, op.value))) })] }), _jsxs("div", { children: [_jsx("label", { className: styles.label, children: "Value" }), renderValueInput()] }), _jsx("button", { onClick: handleApply, disabled: isDisabled || !canApply, className: styles.buttonPrimary, children: "Apply Filter" })] }) }), document.body)] }));
168
198
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reactorui/datagrid",
3
- "version": "1.0.24",
3
+ "version": "1.1.1",
4
4
  "description": "A flexible, high-performance React data grid component with TypeScript support, advanced filtering, pagination, sorting, and customizable theming",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",