@opencorestack/opengridx 1.0.5 → 1.0.6

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.
@@ -7,6 +7,24 @@ import { ColumnVisibilityPanel } from '../ColumnVisibilityPanel/ColumnVisibility
7
7
  import { FilterPanel } from '../FilterPanel/FilterPanel';
8
8
  import { GridTooltip } from '../Tooltip/Tooltip';
9
9
 
10
+ /** Props passed to a custom toolbar button renderer. */
11
+ export interface ToolbarButtonRenderProps {
12
+ /** Call this to toggle the associated panel open/closed. */
13
+ onClick: () => void;
14
+ /** Whether the associated panel is currently open. */
15
+ isOpen: boolean;
16
+ /** Number of active items (filters applied, aggregations configured, etc.). */
17
+ activeCount: number;
18
+ }
19
+
20
+ /** Props passed to a custom quick-filter renderer. */
21
+ export interface ToolbarQuickFilterRenderProps {
22
+ /** Current search string. */
23
+ value: string;
24
+ /** Call with the new search string when the input changes. */
25
+ onChange: (value: string) => void;
26
+ }
27
+
10
28
  export interface GridToolbarProps {
11
29
  columns?: GridColDef[];
12
30
  /** Original user-defined columns (before pivot transformation).
@@ -29,6 +47,33 @@ export interface GridToolbarProps {
29
47
  children?: React.ReactNode;
30
48
  rightContent?: React.ReactNode;
31
49
  style?: React.CSSProperties;
50
+ /**
51
+ * Replace the built-in Columns button. The columns panel still opens and
52
+ * closes normally — only the trigger element is replaced.
53
+ */
54
+ renderColumnsButton?: (props: ToolbarButtonRenderProps) => React.ReactNode;
55
+ /**
56
+ * Replace the built-in Filters button. The filter panel still opens and
57
+ * closes normally — only the trigger element is replaced.
58
+ */
59
+ renderFilterButton?: (props: ToolbarButtonRenderProps) => React.ReactNode;
60
+ /**
61
+ * Replace the built-in Aggregation/Summaries button. The aggregation panel
62
+ * still opens and closes normally — only the trigger element is replaced.
63
+ */
64
+ renderAggregationButton?: (props: ToolbarButtonRenderProps) => React.ReactNode;
65
+ /**
66
+ * Render an Export button. There is no built-in export button — this slot
67
+ * inserts your element after the Aggregation button in the toolbar action row.
68
+ */
69
+ renderExportButton?: () => React.ReactNode;
70
+ /**
71
+ * Replace the built-in quick-filter search bar. Receives the current search
72
+ * value and an onChange handler; connect them to your own input element.
73
+ */
74
+ renderQuickFilter?: (props: ToolbarQuickFilterRenderProps) => React.ReactNode;
75
+ /** Additional CSS class applied to the toolbar root element. */
76
+ className?: string;
32
77
  }
33
78
 
34
79
  const AGGREGATION_FUNCTIONS = ['none', 'sum', 'avg', 'count', 'min', 'max'] as const;
@@ -78,7 +123,7 @@ function AggregationPanel({
78
123
  onClose,
79
124
  activeCount,
80
125
  }: {
81
- anchorRef: React.RefObject<HTMLButtonElement | null>;
126
+ anchorRef: React.RefObject<HTMLElement | null>;
82
127
  aggregationModel: GridAggregationModel;
83
128
  aggregableColumns: GridColDef[];
84
129
  onFunctionChange: (field: string, fn: string) => void;
@@ -205,7 +250,7 @@ function ColumnsPanelWrapper({
205
250
  onColumnOrderReset,
206
251
  onClose
207
252
  }: {
208
- anchorRef: React.RefObject<HTMLButtonElement | null>;
253
+ anchorRef: React.RefObject<HTMLElement | null>;
209
254
  columns: GridColDef[];
210
255
  visibleColumns: Set<string>;
211
256
  onVisibilityChange: (field: string, isVisible: boolean) => void;
@@ -286,7 +331,7 @@ function FilterPanelWrapper({
286
331
  onFilterModelChange,
287
332
  onClose
288
333
  }: {
289
- anchorRef: React.RefObject<HTMLButtonElement | null>;
334
+ anchorRef: React.RefObject<HTMLElement | null>;
290
335
  columns: GridColDef[];
291
336
  filterModel: GridFilterModel;
292
337
  onFilterModelChange: (model: GridFilterModel) => void;
@@ -413,6 +458,12 @@ export function GridToolbar({
413
458
  children,
414
459
  rightContent,
415
460
  style,
461
+ renderColumnsButton,
462
+ renderFilterButton,
463
+ renderAggregationButton,
464
+ renderExportButton,
465
+ renderQuickFilter,
466
+ className,
416
467
  }: GridToolbarProps) {
417
468
  const [aggOpen, setAggOpen] = useState(false);
418
469
  const [pivotOpen, setPivotOpen] = useState(false);
@@ -433,6 +484,12 @@ export function GridToolbar({
433
484
  const colsButtonRef = useRef<HTMLButtonElement>(null);
434
485
  const filterButtonRef = useRef<HTMLButtonElement>(null);
435
486
 
487
+ // Wrapper div refs — used as panel anchors when a custom button renderer
488
+ // is provided (the custom element doesn't expose a ref to us).
489
+ const colsWrapperRef = useRef<HTMLDivElement>(null);
490
+ const filterWrapperRef = useRef<HTMLDivElement>(null);
491
+ const aggWrapperRef = useRef<HTMLDivElement>(null);
492
+
436
493
  const currentPivotModel = pivotModel ?? EMPTY_PIVOT;
437
494
  const pivotActive = currentPivotModel.rowFields.length > 0
438
495
  || currentPivotModel.columnFields.length > 0
@@ -512,7 +569,7 @@ export function GridToolbar({
512
569
  }, [columns, columnVisibilityModel, onColumnVisibilityModelChange]);
513
570
 
514
571
  return (
515
- <div className="ogx-toolbar" role="toolbar" aria-label="Grid toolbar" style={style}>
572
+ <div className={['ogx-toolbar', className].filter(Boolean).join(' ')} role="toolbar" aria-label="Grid toolbar" style={style}>
516
573
  {/* Left slot — custom content */}
517
574
  {children && <div className="ogx-toolbar__left">{children}</div>}
518
575
 
@@ -523,35 +580,49 @@ export function GridToolbar({
523
580
  <div className="ogx-toolbar__actions">
524
581
  {onFilterModelChange && (
525
582
  <div className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
526
- <GlobalSearch
527
- value={searchValue}
528
- onChange={handleSearchChange}
529
- placeholder="Search..."
530
- />
583
+ {renderQuickFilter
584
+ ? renderQuickFilter({ value: searchValue, onChange: handleSearchChange })
585
+ : (
586
+ <GlobalSearch
587
+ value={searchValue}
588
+ onChange={handleSearchChange}
589
+ placeholder="Search..."
590
+ />
591
+ )
592
+ }
531
593
  </div>
532
594
  )}
533
595
 
534
596
  {/* Columns Button */}
535
597
  {onColumnVisibilityModelChange && (
536
- <div className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
537
- <GridTooltip title="Columns">
538
- <button
539
- ref={colsButtonRef}
540
- className={`ogx-toolbar__icon-btn${colsOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
541
- aria-label="Manage columns"
542
- onClick={() => {
543
- setColsOpen(!colsOpen);
544
- setAggOpen(false);
545
- setPivotOpen(false);
546
- setFilterOpen(false);
547
- }}
548
- >
549
- <ViewColumnIcon />
550
- </button>
551
- </GridTooltip>
598
+ <div ref={colsWrapperRef} className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
599
+ {renderColumnsButton
600
+ ? renderColumnsButton({
601
+ onClick: () => { setColsOpen(o => !o); setAggOpen(false); setPivotOpen(false); setFilterOpen(false); },
602
+ isOpen: colsOpen,
603
+ activeCount: columns.length - visibleColumns.size,
604
+ })
605
+ : (
606
+ <GridTooltip title="Columns">
607
+ <button
608
+ ref={colsButtonRef}
609
+ className={`ogx-toolbar__icon-btn${colsOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
610
+ aria-label="Manage columns"
611
+ onClick={() => {
612
+ setColsOpen(!colsOpen);
613
+ setAggOpen(false);
614
+ setPivotOpen(false);
615
+ setFilterOpen(false);
616
+ }}
617
+ >
618
+ <ViewColumnIcon />
619
+ </button>
620
+ </GridTooltip>
621
+ )
622
+ }
552
623
  {colsOpen && (
553
624
  <ColumnsPanelWrapper
554
- anchorRef={colsButtonRef}
625
+ anchorRef={renderColumnsButton ? colsWrapperRef : colsButtonRef}
555
626
  columns={columns}
556
627
  visibleColumns={visibleColumns}
557
628
  onVisibilityChange={handleVisibilityChange}
@@ -567,28 +638,37 @@ export function GridToolbar({
567
638
 
568
639
  {/* Filters Button */}
569
640
  {onFilterModelChange && (
570
- <div className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
571
- <GridTooltip title="Filters">
572
- <button
573
- ref={filterButtonRef}
574
- className={`ogx-toolbar__icon-btn${filterOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
575
- aria-label="Advanced filters"
576
- onClick={() => {
577
- setFilterOpen(!filterOpen);
578
- setAggOpen(false);
579
- setPivotOpen(false);
580
- setColsOpen(false);
581
- }}
582
- >
583
- <FilterIcon />
584
- {activeFilterCount > 0 && (
585
- <span className="ogx-toolbar__dot" aria-label="Filters active" />
586
- )}
587
- </button>
588
- </GridTooltip>
641
+ <div ref={filterWrapperRef} className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
642
+ {renderFilterButton
643
+ ? renderFilterButton({
644
+ onClick: () => { setFilterOpen(o => !o); setAggOpen(false); setPivotOpen(false); setColsOpen(false); },
645
+ isOpen: filterOpen,
646
+ activeCount: activeFilterCount,
647
+ })
648
+ : (
649
+ <GridTooltip title="Filters">
650
+ <button
651
+ ref={filterButtonRef}
652
+ className={`ogx-toolbar__icon-btn${filterOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
653
+ aria-label="Advanced filters"
654
+ onClick={() => {
655
+ setFilterOpen(!filterOpen);
656
+ setAggOpen(false);
657
+ setPivotOpen(false);
658
+ setColsOpen(false);
659
+ }}
660
+ >
661
+ <FilterIcon />
662
+ {activeFilterCount > 0 && (
663
+ <span className="ogx-toolbar__dot" aria-label="Filters active" />
664
+ )}
665
+ </button>
666
+ </GridTooltip>
667
+ )
668
+ }
589
669
  {filterOpen && (
590
670
  <FilterPanelWrapper
591
- anchorRef={filterButtonRef}
671
+ anchorRef={renderFilterButton ? filterWrapperRef : filterButtonRef}
592
672
  columns={columns}
593
673
  filterModel={filterModel || { items: [] }}
594
674
  onFilterModelChange={onFilterModelChange}
@@ -636,32 +716,41 @@ export function GridToolbar({
636
716
  )}
637
717
 
638
718
  {/* Summaries button */}
639
- <div className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
640
- <GridTooltip title="Summaries">
641
- <button
642
- ref={aggButtonRef}
643
- id="ogx-aggregation-btn"
644
- className={`ogx-toolbar__icon-btn${aggOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
645
- aria-label="Configure summaries"
646
- aria-expanded={aggOpen}
647
- aria-controls="ogx-aggregation-panel"
648
- onClick={() => {
649
- setAggOpen(!aggOpen);
650
- setPivotOpen(false);
651
- setColsOpen(false);
652
- setFilterOpen(false);
653
- }}
654
- >
655
- <SigmaIcon />
656
- {activeCount > 0 && (
657
- <span className="ogx-toolbar__dot" aria-label={`${activeCount} active summaries`} />
658
- )}
659
- </button>
660
- </GridTooltip>
719
+ <div ref={aggWrapperRef} className="ogx-toolbar__dropdown-wrapper" style={{ marginRight: 4 }}>
720
+ {renderAggregationButton
721
+ ? renderAggregationButton({
722
+ onClick: () => { setAggOpen(o => !o); setPivotOpen(false); setColsOpen(false); setFilterOpen(false); },
723
+ isOpen: aggOpen,
724
+ activeCount,
725
+ })
726
+ : (
727
+ <GridTooltip title="Summaries">
728
+ <button
729
+ ref={aggButtonRef}
730
+ id="ogx-aggregation-btn"
731
+ className={`ogx-toolbar__icon-btn${aggOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
732
+ aria-label="Configure summaries"
733
+ aria-expanded={aggOpen}
734
+ aria-controls="ogx-aggregation-panel"
735
+ onClick={() => {
736
+ setAggOpen(!aggOpen);
737
+ setPivotOpen(false);
738
+ setColsOpen(false);
739
+ setFilterOpen(false);
740
+ }}
741
+ >
742
+ <SigmaIcon />
743
+ {activeCount > 0 && (
744
+ <span className="ogx-toolbar__dot" aria-label={`${activeCount} active summaries`} />
745
+ )}
746
+ </button>
747
+ </GridTooltip>
748
+ )
749
+ }
661
750
 
662
751
  {aggOpen && (
663
752
  <AggregationPanel
664
- anchorRef={aggButtonRef}
753
+ anchorRef={renderAggregationButton ? aggWrapperRef : aggButtonRef}
665
754
  aggregationModel={aggregationModel}
666
755
  aggregableColumns={aggregableColumns}
667
756
  onFunctionChange={handleFunctionChange}
@@ -672,6 +761,9 @@ export function GridToolbar({
672
761
  )}
673
762
  </div>
674
763
 
764
+ {/* Export button slot */}
765
+ {renderExportButton && renderExportButton()}
766
+
675
767
  {rightContent}
676
768
  </div>
677
769
  </div>
package/lib/index.ts CHANGED
@@ -9,7 +9,7 @@ export { Header } from './components/Header/Header';
9
9
  export { Skeleton } from './components/Skeleton/Skeleton';
10
10
  export { FilterPanel } from './components/FilterPanel';
11
11
  export { GridToolbar } from './components/Toolbar/GridToolbar';
12
- export type { GridToolbarProps } from './components/Toolbar/GridToolbar';
12
+ export type { GridToolbarProps, ToolbarButtonRenderProps, ToolbarQuickFilterRenderProps } from './components/Toolbar/GridToolbar';
13
13
  export { GridTooltip } from './components/Tooltip/Tooltip';
14
14
 
15
15
  export { useGridApiRef } from './hooks/core/useGridApiRef';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencorestack/opengridx",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
5
5
  "type": "module",
6
6
  "main": "./dist/opengridx.umd.js",