@ornery/ui-grid-react 0.1.7-hotfix-1 → 0.1.8

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.
package/src/UiGrid.tsx CHANGED
@@ -3,6 +3,7 @@ import type {
3
3
  GridOptions,
4
4
  GridCellTemplateContext,
5
5
  GridExpandableTemplateContext,
6
+ GridHeaderTemplateContext,
6
7
  UiGridApi,
7
8
  GridColumnDef,
8
9
  GridRow,
@@ -15,6 +16,7 @@ export interface UiGridProps {
15
16
  options: GridOptions;
16
17
  onRegisterApi?: (api: UiGridApi) => void;
17
18
  cellRenderer?: (context: GridCellTemplateContext) => React.ReactNode;
19
+ headerRenderer?: (context: GridHeaderTemplateContext) => React.ReactNode;
18
20
  expandableRenderer?: (context: GridExpandableTemplateContext) => React.ReactNode;
19
21
  className?: string;
20
22
  }
@@ -23,6 +25,7 @@ export function UiGrid({
23
25
  options,
24
26
  onRegisterApi,
25
27
  cellRenderer,
28
+ headerRenderer,
26
29
  expandableRenderer,
27
30
  className,
28
31
  }: UiGridProps) {
@@ -36,12 +39,7 @@ export function UiGrid({
36
39
  gridContainerRef,
37
40
  displayItems,
38
41
  virtualizationEnabled,
39
- pipelineMs,
40
- visibleRowCount,
41
- totalRows,
42
- benchmarkResult,
43
42
  rowSize,
44
- viewportHeightPx,
45
43
  editingValue,
46
44
  sortingFeature,
47
45
  filteringFeature,
@@ -50,7 +48,6 @@ export function UiGrid({
50
48
  cellEditFeature,
51
49
  expandableFeature,
52
50
  treeViewFeature,
53
- csvExportFeature,
54
51
  columnMovingFeature,
55
52
  paginationCurrentPage,
56
53
  paginationTotalPages,
@@ -79,6 +76,25 @@ export function UiGrid({
79
76
  const [dropTargetColumnName, setDropTargetColumnName] = React.useState<string | null>(null);
80
77
  const scrollContainerHeight = `${options.viewportHeight ?? 560}px`;
81
78
 
79
+ function renderHeaderContent(column: GridColumnDef): React.ReactNode {
80
+ const value = state.headerLabel(column);
81
+ const context: GridHeaderTemplateContext = {
82
+ $implicit: value,
83
+ value,
84
+ column,
85
+ };
86
+
87
+ if (headerRenderer) {
88
+ return headerRenderer(context) ?? value;
89
+ }
90
+
91
+ if (column.headerRenderer) {
92
+ return column.headerRenderer(context);
93
+ }
94
+
95
+ return value;
96
+ }
97
+
82
98
  const eventPathIncludesClass = React.useCallback((event: Event, className: string): boolean => {
83
99
  const eventPath =
84
100
  typeof event.composedPath === 'function'
@@ -428,390 +444,290 @@ export function UiGrid({
428
444
 
429
445
  return (
430
446
  <div className={`ui-grid-host ${className ?? ''}`} ref={gridContainerRef}>
431
- <section className="grid-shell ui-grid-shell" data-part="shell">
432
- <header className="grid-hero ui-grid-toolbar-shell" data-part="hero">
433
- <div>
434
- <p className="eyebrow">React wrapper for @ornery/ui-grid</p>
435
- <h1>{options.title ?? 'UI Grid'}</h1>
436
- <p className="deck">
437
- Familiar `gridOptions` and `onRegisterApi`, built with React hooks, virtualization,
438
- grouping, sorting, filtering, and column ordering.
439
- </p>
440
- </div>
441
-
442
- <div className="hero-actions">
443
- <button
444
- type="button"
445
- className="action action-secondary"
446
- data-part="action benchmark-action"
447
- onClick={() => state.runBenchmark()}
448
- >
449
- Benchmark
450
- </button>
451
- {csvExportFeature && (
452
- <button
453
- type="button"
454
- className="action action-secondary"
455
- data-part="action export-action"
456
- onClick={() => state.exportCsv()}
457
- >
458
- Export CSV
459
- </button>
460
- )}
461
- <div className="stats-card" data-part="stats-card">
462
- <span>{visibleRowCount}</span>
463
- <small>{labels.statsVisibleRows}</small>
464
- </div>
465
- </div>
466
- </header>
467
-
468
- <section
469
- className="metrics-strip"
470
- data-part="metrics"
471
- aria-label="Grid performance metrics"
472
- >
473
- <article data-part="metric-card">
474
- <strong>{pipelineMs.toFixed(2)} ms</strong>
475
- <span>pipeline</span>
476
- </article>
477
- <article data-part="metric-card">
478
- <strong>{virtualizationEnabled ? 'On' : 'Off'}</strong>
479
- <span>virtualization</span>
480
- </article>
481
- <article data-part="metric-card">
482
- <strong>{state.groupByColumns.length}</strong>
483
- <span>group columns</span>
484
- </article>
485
- <article data-part="metric-card">
486
- <strong>{benchmarkResult?.averageMs?.toFixed(2) || '—'}</strong>
487
- <span>benchmark avg</span>
488
- </article>
489
- </section>
490
-
491
- <section
492
- className="grid-frame ui-grid"
493
- data-part="grid-frame"
494
- role="grid"
495
- aria-label={options.title ?? 'Data grid'}
447
+ <section
448
+ className="grid-frame ui-grid"
449
+ data-part="grid-frame"
450
+ role="grid"
451
+ aria-label={options.title ?? 'Data grid'}
452
+ >
453
+ <div
454
+ className="grid-table ui-grid-contents-wrapper"
455
+ data-part="grid-table"
456
+ style={
457
+ virtualizationEnabled ? { height: scrollContainerHeight, overflowY: 'auto' } : undefined
458
+ }
459
+ onScroll={virtualizationEnabled ? onGridTableScroll : undefined}
496
460
  >
497
- <div className="grid-toolbar" data-part="grid-toolbar">
498
- <div>
499
- <strong>{visibleRowCount}</strong>
500
- <span>
501
- {labels.toolbarOf} {totalRows} {labels.toolbarRows}
502
- </span>
503
- </div>
504
- <p>
505
- `gridOptions` compatibility layer: sorting, filtering, grouping, column moving,
506
- templating, and virtualized rendering.
507
- </p>
508
- </div>
509
-
510
461
  <div
511
- className="grid-table ui-grid-contents-wrapper"
512
- data-part="grid-table"
513
- style={
514
- virtualizationEnabled
515
- ? { height: scrollContainerHeight, overflowY: 'auto' }
516
- : undefined
517
- }
518
- onScroll={virtualizationEnabled ? onGridTableScroll : undefined}
462
+ className="header-grid ui-grid-header ui-grid-header-canvas"
463
+ data-part="header"
464
+ role="row"
465
+ ref={headerGridRef}
466
+ style={{ gridTemplateColumns }}
519
467
  >
520
- {/* Header row */}
521
- <div
522
- className="header-grid ui-grid-header ui-grid-header-canvas"
523
- data-part="header"
524
- role="row"
525
- ref={headerGridRef}
526
- style={{ gridTemplateColumns }}
527
- >
528
- {visibleColumns.map((column) => {
529
- const pinned = state.isPinned(column);
530
- const pinOffset = pinned ? state.pinnedOffset(column) : null;
531
- const pinMenuOpen = isPinMenuOpen(column);
532
- return (
533
- <div
534
- key={column.name}
535
- className={`header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== 'none' ? ' is-active' : ''}${pinned ? ' is-pinned' : ''}${pinMenuOpen ? ' is-pin-menu-open' : ''}${draggedColumnName === column.name ? ' is-dragging' : ''}${dropTargetColumnName === column.name ? ' is-drag-target' : ''}`}
536
- data-part="header-cell"
537
- role="columnheader"
538
- aria-sort={sortingFeature ? (state.sortAriaSort(column) as any) : undefined}
539
- draggable={columnMovingFeature}
540
- onDragStart={(event) => handleHeaderDragStart(column, event)}
541
- onDragOver={(event) => handleHeaderDragOver(column, event)}
542
- onDrop={(event) => handleHeaderDrop(column, event)}
543
- onDragEnd={handleHeaderDragEnd}
544
- onDragLeave={() => {
545
- if (dropTargetColumnName === column.name) {
546
- setDropTargetColumnName(null);
547
- }
548
- }}
549
- style={{
550
- position: pinned ? 'sticky' : undefined,
551
- left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
552
- right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
553
- zIndex: pinMenuOpen ? 8 : pinned ? 2 : undefined,
554
- }}
555
- >
556
- <span className="header-label">{state.headerLabel(column)}</span>
557
-
558
- <div className="header-actions">
559
- {sortingFeature && (
468
+ {visibleColumns.map((column) => {
469
+ const pinned = state.isPinned(column);
470
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
471
+ const pinMenuOpen = isPinMenuOpen(column);
472
+ return (
473
+ <div
474
+ key={column.name}
475
+ className={`header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== 'none' ? ' is-active' : ''}${pinned ? ' is-pinned' : ''}${pinMenuOpen ? ' is-pin-menu-open' : ''}${draggedColumnName === column.name ? ' is-dragging' : ''}${dropTargetColumnName === column.name ? ' is-drag-target' : ''}`}
476
+ data-part="header-cell"
477
+ role="columnheader"
478
+ aria-sort={sortingFeature ? (state.sortAriaSort(column) as any) : undefined}
479
+ draggable={columnMovingFeature}
480
+ onDragStart={(event) => handleHeaderDragStart(column, event)}
481
+ onDragOver={(event) => handleHeaderDragOver(column, event)}
482
+ onDrop={(event) => handleHeaderDrop(column, event)}
483
+ onDragEnd={handleHeaderDragEnd}
484
+ onDragLeave={() => {
485
+ if (dropTargetColumnName === column.name) {
486
+ setDropTargetColumnName(null);
487
+ }
488
+ }}
489
+ style={{
490
+ position: pinned ? 'sticky' : undefined,
491
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
492
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
493
+ zIndex: pinMenuOpen ? 8 : pinned ? 2 : undefined,
494
+ }}
495
+ >
496
+ <span className="header-label">{renderHeaderContent(column)}</span>
497
+
498
+ <div className="header-actions">
499
+ {sortingFeature && (
500
+ <button
501
+ type="button"
502
+ className={`header-action${!state.isColumnSortable(column) ? ' header-action-disabled' : ''}`}
503
+ disabled={!state.isColumnSortable(column)}
504
+ aria-label={state.sortButtonLabel(column)}
505
+ title={state.sortButtonLabel(column)}
506
+ onClick={() => state.toggleSort(column)}
507
+ >
508
+ {renderSortIcon(column)}
509
+ <span className="sr-only ui-grid-sr-only">
510
+ {state.sortButtonLabel(column)}
511
+ </span>
512
+ </button>
513
+ )}
514
+
515
+ {groupingFeature &&
516
+ state.isGroupingEnabled() &&
517
+ column.enableGrouping !== false && (
560
518
  <button
561
519
  type="button"
562
- className={`header-action${!state.isColumnSortable(column) ? ' header-action-disabled' : ''}`}
563
- disabled={!state.isColumnSortable(column)}
564
- aria-label={state.sortButtonLabel(column)}
565
- title={state.sortButtonLabel(column)}
566
- onClick={() => state.toggleSort(column)}
520
+ className={`chip-action${state.isGrouped(column) ? ' chip-action-active' : ''}`}
521
+ data-part="group-toggle"
522
+ aria-label={state.groupingButtonLabel(column)}
523
+ title={state.groupingButtonLabel(column)}
524
+ onClick={(e) => state.toggleGrouping(column, e)}
567
525
  >
568
- {renderSortIcon(column)}
526
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
527
+ <path d="M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" />
528
+ </svg>
569
529
  <span className="sr-only ui-grid-sr-only">
570
- {state.sortButtonLabel(column)}
530
+ {state.groupingButtonLabel(column)}
571
531
  </span>
572
532
  </button>
573
533
  )}
574
534
 
575
- {groupingFeature &&
576
- state.isGroupingEnabled() &&
577
- column.enableGrouping !== false && (
535
+ {state.pinningFeature &&
536
+ state.isPinningEnabled() &&
537
+ state.isColumnPinnable(column) && (
538
+ <div
539
+ className={`pin-control${pinMenuOpen ? ' pin-control-open' : ''}`}
540
+ onClick={(event) => event.stopPropagation()}
541
+ >
578
542
  <button
579
543
  type="button"
580
- className={`chip-action${state.isGrouped(column) ? ' chip-action-active' : ''}`}
581
- data-part="group-toggle"
582
- aria-label={state.groupingButtonLabel(column)}
583
- title={state.groupingButtonLabel(column)}
584
- onClick={(e) => state.toggleGrouping(column, e)}
544
+ className={`chip-action pin-trigger${pinned || pinMenuOpen ? ' chip-action-active' : ''}`}
545
+ data-part="pin-toggle"
546
+ aria-label={pinButtonLabel(column)}
547
+ title={pinButtonLabel(column)}
548
+ aria-haspopup={pinned ? undefined : 'menu'}
549
+ aria-expanded={pinned ? undefined : pinMenuOpen}
550
+ onClick={(event) => onPinTrigger(column, event)}
585
551
  >
586
552
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
587
- <path d="M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" />
553
+ <path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" />
588
554
  </svg>
589
- <span className="sr-only ui-grid-sr-only">
590
- {state.groupingButtonLabel(column)}
591
- </span>
555
+ <span className="sr-only ui-grid-sr-only">{pinButtonLabel(column)}</span>
592
556
  </button>
593
- )}
594
- {state.pinningFeature &&
595
- state.isPinningEnabled() &&
596
- state.isColumnPinnable(column) && (
557
+
597
558
  <div
598
- className={`pin-control${pinMenuOpen ? ' pin-control-open' : ''}`}
599
- onClick={(event) => event.stopPropagation()}
559
+ className="pin-menu"
560
+ data-part="pin-menu"
561
+ role="menu"
562
+ aria-label="Pin options"
563
+ aria-hidden={!pinMenuOpen}
600
564
  >
601
565
  <button
602
566
  type="button"
603
- className={`chip-action pin-trigger${pinned || pinMenuOpen ? ' chip-action-active' : ''}`}
604
- data-part="pin-toggle"
605
- aria-label={pinButtonLabel(column)}
606
- title={pinButtonLabel(column)}
607
- aria-haspopup={pinned ? undefined : 'menu'}
608
- aria-expanded={pinned ? undefined : pinMenuOpen}
609
- onClick={(event) => onPinTrigger(column, event)}
567
+ className="pin-menu-action"
568
+ data-part="pin-left-action"
569
+ role="menuitem"
570
+ aria-label={labels.pinLeft}
571
+ title={labels.pinLeft}
572
+ tabIndex={pinMenuOpen ? 0 : -1}
573
+ onClick={(event) => choosePinDirection(column, 'left', event)}
610
574
  >
611
575
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
612
- <path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" />
576
+ <path d="M10 6 4 12l6 6v-4h10v-4H10V6z" />
613
577
  </svg>
614
- <span className="sr-only ui-grid-sr-only">
615
- {pinButtonLabel(column)}
616
- </span>
578
+ <span className="sr-only ui-grid-sr-only">{labels.pinLeft}</span>
617
579
  </button>
618
-
619
- <div
620
- className="pin-menu"
621
- data-part="pin-menu"
622
- role="menu"
623
- aria-label="Pin options"
624
- aria-hidden={!pinMenuOpen}
580
+ <button
581
+ type="button"
582
+ className="pin-menu-action"
583
+ data-part="pin-right-action"
584
+ role="menuitem"
585
+ aria-label={labels.pinRight}
586
+ title={labels.pinRight}
587
+ tabIndex={pinMenuOpen ? 0 : -1}
588
+ onClick={(event) => choosePinDirection(column, 'right', event)}
625
589
  >
626
- <button
627
- type="button"
628
- className="pin-menu-action"
629
- data-part="pin-left-action"
630
- role="menuitem"
631
- aria-label={labels.pinLeft}
632
- title={labels.pinLeft}
633
- tabIndex={pinMenuOpen ? 0 : -1}
634
- onClick={(event) => choosePinDirection(column, 'left', event)}
635
- >
636
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
637
- <path d="M10 6 4 12l6 6v-4h10v-4H10V6z" />
638
- </svg>
639
- <span className="sr-only ui-grid-sr-only">{labels.pinLeft}</span>
640
- </button>
641
- <button
642
- type="button"
643
- className="pin-menu-action"
644
- data-part="pin-right-action"
645
- role="menuitem"
646
- aria-label={labels.pinRight}
647
- title={labels.pinRight}
648
- tabIndex={pinMenuOpen ? 0 : -1}
649
- onClick={(event) => choosePinDirection(column, 'right', event)}
650
- >
651
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
652
- <path d="M14 6v4H4v4h10v4l6-6-6-6z" />
653
- </svg>
654
- <span className="sr-only ui-grid-sr-only">{labels.pinRight}</span>
655
- </button>
656
- </div>
590
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
591
+ <path d="M14 6v4H4v4h10v4l6-6-6-6z" />
592
+ </svg>
593
+ <span className="sr-only ui-grid-sr-only">{labels.pinRight}</span>
594
+ </button>
657
595
  </div>
658
- )}
659
- </div>
596
+ </div>
597
+ )}
660
598
  </div>
661
- );
662
- })}
663
- </div>
664
-
665
- {/* Filter row */}
666
- {filteringFeature && state.isFilteringEnabled() && (
667
- <div
668
- className="filter-grid ui-grid-header"
669
- data-part="filters"
670
- ref={filterGridRef}
671
- style={{
672
- gridTemplateColumns,
673
- ['--ui-grid-header-sticky-top' as string]: `${headerStickyHeight}px`,
674
- }}
675
- >
676
- {visibleColumns.map((column) => {
677
- const pinned = state.isPinned(column);
678
- const pinOffset = pinned ? state.pinnedOffset(column) : null;
679
- return (
680
- <label
681
- key={column.name}
682
- className={`filter-cell ui-grid-filter-container${pinned ? ' is-pinned' : ''}`}
683
- data-part="filter-cell"
684
- style={{
685
- position: pinned ? 'sticky' : undefined,
686
- left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
687
- right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
688
- zIndex: pinned ? 2 : undefined,
689
- }}
690
- >
691
- <span className="sr-only ui-grid-sr-only">
692
- {labels.filterColumn} {state.headerLabel(column)}
693
- </span>
694
- <input
695
- className="ui-grid-filter-input"
696
- type="text"
697
- defaultValue={state.filterValue(column.name)}
698
- placeholder={state.filterPlaceholder(column)}
699
- disabled={state.isFilterInputDisabled(column)}
700
- onChange={(e) => state.updateFilter(column.name, e.target.value)}
701
- />
702
- </label>
703
- );
704
- })}
705
- </div>
706
- )}
599
+ </div>
600
+ );
601
+ })}
602
+ </div>
707
603
 
708
- {/* Body */}
709
- {displayItems.length > 0 ? (
710
- virtualizationEnabled ? (
711
- <div
712
- className="grid-virtual-spacer"
713
- style={{ height: `${virtualScroll.totalHeight}px` }}
714
- >
715
- <div
716
- className="body-grid ui-grid-canvas grid-virtual-body"
717
- data-part="body"
718
- role="rowgroup"
604
+ {filteringFeature && state.isFilteringEnabled() && (
605
+ <div
606
+ className="filter-grid ui-grid-header"
607
+ data-part="filters"
608
+ ref={filterGridRef}
609
+ style={{
610
+ gridTemplateColumns,
611
+ ['--ui-grid-header-sticky-top' as string]: `${headerStickyHeight}px`,
612
+ }}
613
+ >
614
+ {visibleColumns.map((column) => {
615
+ const pinned = state.isPinned(column);
616
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
617
+ return (
618
+ <label
619
+ key={column.name}
620
+ className={`filter-cell ui-grid-filter-container${pinned ? ' is-pinned' : ''}`}
621
+ data-part="filter-cell"
719
622
  style={{
720
- gridTemplateColumns,
721
- position: 'absolute',
722
- top: `${virtualScroll.offsetY}px`,
723
- left: 0,
623
+ position: pinned ? 'sticky' : undefined,
624
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
625
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
626
+ zIndex: pinned ? 2 : undefined,
724
627
  }}
725
628
  >
726
- {itemsToRender.map(renderDisplayItem)}
727
- </div>
728
- </div>
729
- ) : (
629
+ <span className="sr-only ui-grid-sr-only">
630
+ {labels.filterColumn} {state.headerLabel(column)}
631
+ </span>
632
+ <input
633
+ className="ui-grid-filter-input"
634
+ type="text"
635
+ defaultValue={state.filterValue(column.name)}
636
+ placeholder={state.filterPlaceholder(column)}
637
+ disabled={state.isFilterInputDisabled(column)}
638
+ onChange={(e) => state.updateFilter(column.name, e.target.value)}
639
+ />
640
+ </label>
641
+ );
642
+ })}
643
+ </div>
644
+ )}
645
+
646
+ {displayItems.length > 0 ? (
647
+ virtualizationEnabled ? (
648
+ <div className="grid-virtual-spacer" style={{ height: `${virtualScroll.totalHeight}px` }}>
730
649
  <div
731
- className="body-grid ui-grid-canvas"
650
+ className="body-grid ui-grid-canvas grid-virtual-body"
732
651
  data-part="body"
733
652
  role="rowgroup"
734
- style={{ gridTemplateColumns }}
653
+ style={{
654
+ gridTemplateColumns,
655
+ position: 'absolute',
656
+ top: `${virtualScroll.offsetY}px`,
657
+ left: 0,
658
+ }}
735
659
  >
736
- {displayItems.map(renderDisplayItem)}
660
+ {itemsToRender.map(renderDisplayItem)}
737
661
  </div>
738
- )
662
+ </div>
739
663
  ) : (
740
- <div className="empty-state ui-grid-no-row-overlay" data-part="empty-state">
741
- <strong>{options.emptyMessage ?? labels.emptyHeading}</strong>
742
- <p>{labels.emptyDescription}</p>
664
+ <div className="body-grid ui-grid-canvas" data-part="body" role="rowgroup" style={{ gridTemplateColumns }}>
665
+ {displayItems.map(renderDisplayItem)}
743
666
  </div>
744
- )}
667
+ )
668
+ ) : (
669
+ <div className="empty-state ui-grid-no-row-overlay" data-part="empty-state">
670
+ <strong>{options.emptyMessage ?? labels.emptyHeading}</strong>
671
+ <p>{labels.emptyDescription}</p>
672
+ </div>
673
+ )}
674
+ </div>
745
675
 
746
- {/* Pagination footer */}
747
- {paginationFeature && state.showPaginationControls() && (
748
- <footer
749
- className="pagination-bar ui-grid-pagination"
750
- data-part="pagination"
751
- role="navigation"
752
- aria-label={labels.paginationPage}
676
+ {paginationFeature && state.showPaginationControls() && (
677
+ <footer
678
+ className="pagination-bar ui-grid-pagination"
679
+ data-part="pagination"
680
+ role="navigation"
681
+ aria-label={labels.paginationPage}
682
+ >
683
+ <p>{state.paginationSummary()}</p>
684
+ <div className="pagination-controls">
685
+ <button
686
+ type="button"
687
+ className="action action-secondary pagination-button"
688
+ aria-label={labels.paginationPrevious}
689
+ disabled={paginationCurrentPage <= 1}
690
+ onClick={() => state.previousPage()}
753
691
  >
754
- <p>{state.paginationSummary()}</p>
755
- <div className="pagination-controls">
756
- <button
757
- type="button"
758
- className="action action-secondary pagination-button"
759
- aria-label={labels.paginationPrevious}
760
- disabled={paginationCurrentPage <= 1}
761
- onClick={() => state.previousPage()}
762
- >
763
- <svg
764
- className="pagination-icon"
765
- viewBox="0 0 24 24"
766
- aria-hidden="true"
767
- focusable={false}
768
- >
769
- <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
770
- </svg>
771
- <span className="sr-only">{labels.paginationPrevious}</span>
772
- </button>
773
- <span>
774
- {labels.paginationPage} {paginationCurrentPage} {labels.paginationOf}{' '}
775
- {paginationTotalPages}
776
- </span>
777
- <button
778
- type="button"
779
- className="action action-secondary pagination-button"
780
- aria-label={labels.paginationNext}
781
- disabled={paginationCurrentPage >= paginationTotalPages}
782
- onClick={() => state.nextPage()}
692
+ <svg className="pagination-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
693
+ <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
694
+ </svg>
695
+ <span className="sr-only">{labels.paginationPrevious}</span>
696
+ </button>
697
+ <span>
698
+ {labels.paginationPage} {paginationCurrentPage} {labels.paginationOf} {paginationTotalPages}
699
+ </span>
700
+ <button
701
+ type="button"
702
+ className="action action-secondary pagination-button"
703
+ aria-label={labels.paginationNext}
704
+ disabled={paginationCurrentPage >= paginationTotalPages}
705
+ onClick={() => state.nextPage()}
706
+ >
707
+ <svg className="pagination-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
708
+ <path d="M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" />
709
+ </svg>
710
+ <span className="sr-only">{labels.paginationNext}</span>
711
+ </button>
712
+ {state.pageSizeOptions().length > 0 && (
713
+ <label className="pagination-size">
714
+ <span className="sr-only">{labels.paginationRows}</span>
715
+ <select
716
+ aria-label={labels.paginationRows}
717
+ value={paginationSelectedPageSize}
718
+ onChange={(e) => state.onPageSizeChange(e.target.value)}
783
719
  >
784
- <svg
785
- className="pagination-icon"
786
- viewBox="0 0 24 24"
787
- aria-hidden="true"
788
- focusable={false}
789
- >
790
- <path d="M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" />
791
- </svg>
792
- <span className="sr-only">{labels.paginationNext}</span>
793
- </button>
794
- {state.pageSizeOptions().length > 0 && (
795
- <label className="pagination-size">
796
- <span className="sr-only">{labels.paginationRows}</span>
797
- <select
798
- aria-label={labels.paginationRows}
799
- value={paginationSelectedPageSize}
800
- onChange={(e) => state.onPageSizeChange(e.target.value)}
801
- >
802
- {state.pageSizeOptions().map((size) => (
803
- <option key={size} value={size}>
804
- {size}
805
- </option>
806
- ))}
807
- </select>
808
- </label>
809
- )}
810
- </div>
811
- </footer>
812
- )}
813
- </div>
814
- </section>
720
+ {state.pageSizeOptions().map((size) => (
721
+ <option key={size} value={size}>
722
+ {size}
723
+ </option>
724
+ ))}
725
+ </select>
726
+ </label>
727
+ )}
728
+ </div>
729
+ </footer>
730
+ )}
815
731
  </section>
816
732
  </div>
817
733
  );