@opencorestack/opengridx 3.8.0 → 3.9.0
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/CHANGELOG.md +47 -0
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.es.js.map +1 -1
- package/dist/index.d.ts +342 -3
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +6952 -6697
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +17 -17
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +121 -4
- package/docs/customization/theming.md +11 -2
- package/docs/features/ai-toolkit.md +1 -1
- package/lib/components/AiAssistant/GridAiAssistantPanel.css +2 -2
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +16 -14
- package/lib/components/Cell/Cell.css +1 -3
- package/lib/components/Cell/CellErrorBoundary.tsx +17 -10
- package/lib/components/CellSelectionStats/CellSelectionStats.tsx +5 -3
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +1 -2
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +3 -1
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +6 -4
- package/lib/components/DataGrid/DataGrid.css +1 -28
- package/lib/components/DataGrid/GridAggregationFooter.tsx +3 -1
- package/lib/components/DataGrid/GridErrorOverlay.tsx +4 -2
- package/lib/components/DataGrid/GridListView.tsx +7 -5
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -3
- package/lib/components/DataGrid/GridLoadingOverlay.tsx +3 -1
- package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
- package/lib/components/DataGrid/GridRoot.tsx +8 -1
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +3 -1
- package/lib/components/DataGrid/GridViewport.tsx +3 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +3 -2
- package/lib/components/DataGrid/gridLocaleContext.ts +13 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +25 -56
- package/lib/components/Header/ColumnMenu.css +5 -5
- package/lib/components/Header/ColumnMenu.tsx +17 -15
- package/lib/components/Header/Header.css +2 -11
- package/lib/components/Header/Header.tsx +8 -6
- package/lib/components/HeaderFilter/HeaderFilterCell.tsx +6 -37
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +3 -1
- package/lib/components/ListView/ListView.css +0 -16
- package/lib/components/ListView/ListViewRow.tsx +3 -1
- package/lib/components/Pagination/Pagination.tsx +15 -18
- package/lib/components/QuickFilter/QuickFilter.tsx +5 -2
- package/lib/components/Row/Row.tsx +9 -4
- package/lib/components/Toolbar/GlobalSearch.tsx +6 -3
- package/lib/components/Toolbar/GridToolbar.tsx +35 -29
- package/lib/components/Toolbar/PivotPanel.tsx +26 -24
- package/lib/components/ui/Checkbox.css +5 -5
- package/lib/components/ui/DragHandleIcon.tsx +3 -1
- package/lib/components/ui/ExpandIcon.css +3 -3
- package/lib/components/ui/ExpandIcon.tsx +3 -1
- package/lib/hooks/core/useGridRowInteractions.ts +13 -3
- package/lib/hooks/core/useGridToolbarStack.ts +2 -2
- package/lib/hooks/core/useGridVirtualization.ts +40 -25
- package/lib/hooks/core/useShallowStableValue.ts +29 -0
- package/lib/hooks/features/useGridAiAssistant.ts +52 -22
- package/lib/hooks/features/useGridSpanning.ts +1 -1
- package/lib/styles/base/reset.css +6 -7
- package/lib/theme/DataGridThemeProvider.tsx +9 -0
- package/lib/theme/__baseline__/default-theme.darwin.chromium.json +1 -1503
- package/lib/theme/__baseline__/default-theme.darwin.firefox.json +1 -1503
- package/lib/theme/__baseline__/default-theme.darwin.webkit.json +1 -1503
- package/lib/theme/types.ts +18 -0
- package/lib/types/locale.ts +344 -0
- package/lib/types/rows.ts +0 -2
- package/lib/utils/cellSelection.ts +0 -2
- package/lib/utils/editing/index.ts +1 -1
- package/lib/utils/filtering/filterInput.ts +45 -0
- package/lib/utils/filtering/index.ts +0 -28
- package/lib/utils/gridLocale.ts +241 -0
- package/lib/utils/pinning/index.ts +0 -107
- package/lib/utils/sorting/index.ts +42 -33
- package/lib/utils/values.ts +5 -1
- package/llms.txt +9 -3
- package/package.json +1 -1
- package/lib/theme/defaultStyles.ts +0 -0
|
@@ -11,6 +11,7 @@ import { getHeaderLineClamp, shouldWrapHeaderText } from '../../utils/headerWrap
|
|
|
11
11
|
import { getHeaderColumnStyle } from './headerColumnStyle';
|
|
12
12
|
import { HeaderFilterRow } from '../HeaderFilter/HeaderFilterRow';
|
|
13
13
|
import { isRtl, logicalArrowKey } from '../../utils/direction';
|
|
14
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
function MenuIcon() {
|
|
@@ -93,6 +94,7 @@ export interface HeaderProps<R extends GridRowModel = GridRowModel> {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps<R>) {
|
|
97
|
+
const locale = useGridLocale();
|
|
96
98
|
const {
|
|
97
99
|
columns,
|
|
98
100
|
allColumns,
|
|
@@ -348,11 +350,11 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
348
350
|
<div
|
|
349
351
|
className={`ogx__header-cell ogx__header-cell--pinned ogx__header-cell--pinned-left ${isHeaderFocused('__reorder_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
350
352
|
role="columnheader"
|
|
351
|
-
aria-label=
|
|
353
|
+
aria-label={locale.headerRowReorderLabel}
|
|
352
354
|
aria-colindex={reorderColIndex}
|
|
353
355
|
data-field="__reorder_col__"
|
|
354
356
|
tabIndex={-1}
|
|
355
|
-
title=
|
|
357
|
+
title={locale.headerRowReorderTooltip}
|
|
356
358
|
style={{
|
|
357
359
|
minWidth: 48,
|
|
358
360
|
maxWidth: 48,
|
|
@@ -368,7 +370,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
368
370
|
className={`ogx__header-cell ogx__header-cell--expand ${pinExpandColumn ? 'ogx__header-cell--pinned ogx__header-cell--pinned-left' : ''
|
|
369
371
|
} ${isHeaderFocused('__expand_col__') ? 'ogx__header-cell--focused' : ''}`}
|
|
370
372
|
role="columnheader"
|
|
371
|
-
aria-label=
|
|
373
|
+
aria-label={locale.headerDetailPanelLabel}
|
|
372
374
|
aria-colindex={expandColIndex}
|
|
373
375
|
data-field="__expand_col__"
|
|
374
376
|
tabIndex={-1}
|
|
@@ -405,7 +407,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
405
407
|
checked={allSelected}
|
|
406
408
|
indeterminate={someSelected}
|
|
407
409
|
onChange={(e) => onSelectAll(e.target.checked)}
|
|
408
|
-
aria-label={allSelected ?
|
|
410
|
+
aria-label={allSelected ? locale.headerDeselectAllLabel : locale.headerSelectAllLabel}
|
|
409
411
|
tabIndex={-1}
|
|
410
412
|
onMouseDown={(e) => e.preventDefault()}
|
|
411
413
|
/>
|
|
@@ -434,7 +436,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
434
436
|
: sortPosition === 0 ? (sortDirection === 'asc' ? 'ascending' : 'descending')
|
|
435
437
|
: 'none';
|
|
436
438
|
const sortDescription = isSorted && sortModel.length > 1
|
|
437
|
-
?
|
|
439
|
+
? locale.headerSortDescription(sortDirection === 'asc' ? 'asc' : 'desc', sortPosition + 1, sortModel.length)
|
|
438
440
|
: undefined;
|
|
439
441
|
const isResizable = colDef.resizable !== false;
|
|
440
442
|
const pinnedPosition = isColumnPinned(colDef.field, pinnedColumns);
|
|
@@ -531,7 +533,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
|
|
|
531
533
|
type="button"
|
|
532
534
|
className="ogx__menu-icon-btn"
|
|
533
535
|
onClick={handleMenuOpen(colDef)}
|
|
534
|
-
aria-label={
|
|
536
|
+
aria-label={locale.headerColumnMenuButtonLabel(colDef.headerName || colDef.field)}
|
|
535
537
|
aria-haspopup="menu"
|
|
536
538
|
tabIndex={-1}
|
|
537
539
|
>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
getHeaderFilterOperators,
|
|
11
11
|
type GridHeaderFilterState,
|
|
12
12
|
} from '../../utils/headerFilters';
|
|
13
|
+
import { filterValueFromInputText, getFilterValueOptions, isSameFilterValue, matchesFilterValueOption } from '../../utils/filtering/filterInput';
|
|
13
14
|
import { Input } from '../ui/Input';
|
|
14
15
|
import { HeaderFilterOperatorMenu } from './HeaderFilterOperatorMenu';
|
|
15
16
|
import type { HeaderFilterLocale } from '../../utils/headerFilterLocale';
|
|
@@ -29,42 +30,10 @@ export interface HeaderFilterCellProps {
|
|
|
29
30
|
locale: HeaderFilterLocale;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
interface ValueOption {
|
|
33
|
-
key: string;
|
|
34
|
-
value: unknown;
|
|
35
|
-
label: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getValueOptions(col: GridColDef): ValueOption[] {
|
|
39
|
-
return (col.valueOptions ?? []).map((option, index) => (
|
|
40
|
-
option !== null && typeof option === 'object'
|
|
41
|
-
? { key: String(index), value: option.value, label: option.label }
|
|
42
|
-
: { key: String(index), value: option, label: String(option) }
|
|
43
|
-
));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function matchesOption(option: ValueOption, value: unknown): boolean {
|
|
47
|
-
const single = Array.isArray(value) ? value[0] : value;
|
|
48
|
-
if (Object.is(option.value, single)) return true;
|
|
49
|
-
return single != null && String(option.value).toLowerCase() === String(single).toLowerCase();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** The item value for typed text. isAnyOf takes a comma-separated list. */
|
|
53
|
-
function fromInputText(text: string, operator: GridFilterOperator): unknown {
|
|
54
|
-
if (operator === 'isAnyOf') return text.split(',').map(part => part.trim()).filter(part => part !== '');
|
|
55
|
-
return text;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function sameValue(a: unknown, b: unknown): boolean {
|
|
59
|
-
if (Object.is(a, b)) return true;
|
|
60
|
-
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
33
|
function sameItem(a: GridFilterItem | undefined, b: GridFilterItem | undefined): boolean {
|
|
65
34
|
if (a === b) return true;
|
|
66
35
|
if (!a || !b) return false;
|
|
67
|
-
return a.operator === b.operator &&
|
|
36
|
+
return a.operator === b.operator && isSameFilterValue(a.value, b.value);
|
|
68
37
|
}
|
|
69
38
|
|
|
70
39
|
function ClearIcon() {
|
|
@@ -81,7 +50,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
|
|
|
81
50
|
const label = colDef.headerName || colDef.field;
|
|
82
51
|
const item = state.kind === 'editable' ? state.item : undefined;
|
|
83
52
|
const operator: GridFilterOperator = item?.operator ?? getHeaderFilterDefaultOperator(colDef);
|
|
84
|
-
const valueOptions =
|
|
53
|
+
const valueOptions = getFilterValueOptions(colDef);
|
|
85
54
|
const usesSelect = colDef.type === 'boolean' || (colDef.type === 'singleSelect' && valueOptions.length > 0);
|
|
86
55
|
const showsValue = !NO_VALUE_OPERATORS.has(operator);
|
|
87
56
|
|
|
@@ -117,7 +86,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
|
|
|
117
86
|
const unchanged = current ? text === filterValueToInputText(current.value, col.type) : text.trim() === '';
|
|
118
87
|
if (unchanged) return;
|
|
119
88
|
const op = current?.operator ?? getHeaderFilterDefaultOperator(col);
|
|
120
|
-
const next = buildHeaderFilterItem(col, op,
|
|
89
|
+
const next = buildHeaderFilterItem(col, op, filterValueFromInputText(text, op));
|
|
121
90
|
setLastEmitted(next);
|
|
122
91
|
emit(col.field, next);
|
|
123
92
|
}, []);
|
|
@@ -173,7 +142,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
|
|
|
173
142
|
|
|
174
143
|
const selectOperator = (op: GridFilterOperator) => {
|
|
175
144
|
// Keep what was typed even if the debounce has not committed it yet.
|
|
176
|
-
const value = draft !== null ?
|
|
145
|
+
const value = draft !== null ? filterValueFromInputText(draft, op) : item?.value;
|
|
177
146
|
setDraft(null);
|
|
178
147
|
setMenuAnchor(null);
|
|
179
148
|
report(buildHeaderFilterItem(colDef, op, convertHeaderFilterValue(value, op)));
|
|
@@ -271,7 +240,7 @@ export function HeaderFilterCell({ colDef, state, style, className, ariaColIndex
|
|
|
271
240
|
</select>
|
|
272
241
|
);
|
|
273
242
|
} else if (usesSelect) {
|
|
274
|
-
const selected = valueOptions.find(o =>
|
|
243
|
+
const selected = valueOptions.find(o => matchesFilterValueOption(o, item?.value));
|
|
275
244
|
valueControl = (
|
|
276
245
|
<select
|
|
277
246
|
ref={selectRef}
|
|
@@ -6,6 +6,7 @@ import { resolveHeaderFilterLocale } from '../../utils/headerFilterLocale';
|
|
|
6
6
|
import { getHeaderFilterState, hasHeaderFilter, setHeaderFilterItem } from '../../utils/headerFilters';
|
|
7
7
|
import { getHeaderColumnStyle } from '../Header/headerColumnStyle';
|
|
8
8
|
import { HeaderFilterCell } from './HeaderFilterCell';
|
|
9
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
9
10
|
|
|
10
11
|
export interface HeaderFilterRowProps {
|
|
11
12
|
/** The rendered columns of the column header row (render window with spacers), in render order. */
|
|
@@ -68,6 +69,7 @@ function SystemFilterCell({ field, ariaColIndex, sticky, left, focused }: {
|
|
|
68
69
|
* through the root-level items it owns (`header:<field>`).
|
|
69
70
|
*/
|
|
70
71
|
export function HeaderFilterRow(props: HeaderFilterRowProps) {
|
|
72
|
+
const gridLocale = useGridLocale();
|
|
71
73
|
const {
|
|
72
74
|
columns, columnWidths, pinnedColumns, pinnedPositions, pinnedEdges, rowReordering, hasDetailPanel,
|
|
73
75
|
checkboxSelection, pinExpandColumn, pinCheckboxColumn, columnIndexMap, ariaRowIndex, filterModel,
|
|
@@ -137,7 +139,7 @@ export function HeaderFilterRow(props: HeaderFilterRowProps) {
|
|
|
137
139
|
className={`ogx__header-filter-cell ogx__header-filter-cell--empty ${className}`}
|
|
138
140
|
style={style}
|
|
139
141
|
role="columnheader"
|
|
140
|
-
aria-label={
|
|
142
|
+
aria-label={gridLocale.headerFilterNoFilterLabel(colDef.headerName || colDef.field)}
|
|
141
143
|
aria-colindex={ariaColIndex}
|
|
142
144
|
data-field={colDef.field}
|
|
143
145
|
tabIndex={-1}
|
|
@@ -109,22 +109,6 @@
|
|
|
109
109
|
color: var(--ogx-text-secondary, #9ca3af);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/* ─── Responsive: auto switch at ≤768px when using ogx--list-auto class ─── */
|
|
113
|
-
@media (max-width: 768px) {
|
|
114
|
-
.ogx--list-auto .ogx__viewport {
|
|
115
|
-
display: none;
|
|
116
|
-
}
|
|
117
|
-
.ogx--list-auto .ogx-list-view {
|
|
118
|
-
display: flex;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@media (min-width: 769px) {
|
|
123
|
-
.ogx--list-auto .ogx-list-view {
|
|
124
|
-
display: none;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
112
|
/* Loading with no rows yet: a progress bar, or slots.loadingOverlay centred. */
|
|
129
113
|
.ogx-list-view__loading {
|
|
130
114
|
position: relative;
|
|
@@ -4,6 +4,7 @@ import { ExpandIcon } from '../ui/ExpandIcon';
|
|
|
4
4
|
import { Checkbox } from '../ui/Checkbox';
|
|
5
5
|
import { CellErrorBoundary } from '../Cell/CellErrorBoundary';
|
|
6
6
|
import { isSyntheticRowId } from '../../utils/syntheticRows';
|
|
7
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
7
8
|
import type { GridColDef, GridRowModel, GridRowId, GridRowMeta, GridListViewColDef, GridRenderCellParams } from '../../types';
|
|
8
9
|
|
|
9
10
|
export interface ListViewRowProps<R extends GridRowModel = GridRowModel> {
|
|
@@ -71,6 +72,7 @@ export function ListViewRow<R extends GridRowModel = GridRowModel>({
|
|
|
71
72
|
rowMeta,
|
|
72
73
|
};
|
|
73
74
|
|
|
75
|
+
const locale = useGridLocale();
|
|
74
76
|
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
75
77
|
e.stopPropagation();
|
|
76
78
|
onSelectionChange?.(id, e.target.checked);
|
|
@@ -96,7 +98,7 @@ export function ListViewRow<R extends GridRowModel = GridRowModel>({
|
|
|
96
98
|
{!isSyntheticRow && <Checkbox
|
|
97
99
|
checked={isSelected}
|
|
98
100
|
onChange={handleCheckboxChange}
|
|
99
|
-
aria-label={
|
|
101
|
+
aria-label={locale.rowSelectLabel(id)}
|
|
100
102
|
// Not a Tab stop of its own: the list is one Tab stop, Shift+Space selects the focused row.
|
|
101
103
|
tabIndex={-1}
|
|
102
104
|
/>}
|
|
@@ -3,6 +3,7 @@ import React, { useId } from 'react';
|
|
|
3
3
|
import { Button } from '../ui/Button';
|
|
4
4
|
import { normalizePageSize, getPageCount, clampPage } from '../../utils/pagination';
|
|
5
5
|
import type { GridLocaleText } from '../../types';
|
|
6
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
6
7
|
|
|
7
8
|
export interface PaginationProps {
|
|
8
9
|
page: number;
|
|
@@ -18,6 +19,7 @@ export interface PaginationProps {
|
|
|
18
19
|
|
|
19
20
|
export function Pagination(props: PaginationProps) {
|
|
20
21
|
const pageSizeId = useId();
|
|
22
|
+
const locale = useGridLocale();
|
|
21
23
|
const {
|
|
22
24
|
page,
|
|
23
25
|
pageSize,
|
|
@@ -43,15 +45,10 @@ export function Pagination(props: PaginationProps) {
|
|
|
43
45
|
|
|
44
46
|
const firstRowIndex = currentPage * safePageSize;
|
|
45
47
|
const lastRowIndex = Math.min(firstRowIndex + safePageSize, rowCount);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
? localeText.paginationOf(rowCount === 0 ? 0 : firstRowIndex + 1, lastRowIndex, rowCount)
|
|
51
|
-
: `${displayedRows} of ${rowCount}`;
|
|
52
|
-
const pageLabel = localeText?.paginationPage
|
|
53
|
-
? localeText.paginationPage(currentPage + 1, pageCount)
|
|
54
|
-
: `Page ${currentPage + 1} of ${pageCount}`;
|
|
48
|
+
// An explicit `localeText` prop (standalone use) wins over the grid's strings.
|
|
49
|
+
const rowsPerPageLabel = localeText?.paginationRowsPerPage ?? locale.paginationRowsPerPage;
|
|
50
|
+
const ofLabel = (localeText?.paginationOf ?? locale.paginationOf)(rowCount === 0 ? 0 : firstRowIndex + 1, lastRowIndex, rowCount);
|
|
51
|
+
const pageLabel = (localeText?.paginationPage ?? locale.paginationPage)(currentPage + 1, pageCount);
|
|
55
52
|
|
|
56
53
|
const handleFirstPage = () => {
|
|
57
54
|
onPageChange(0);
|
|
@@ -79,7 +76,7 @@ export function Pagination(props: PaginationProps) {
|
|
|
79
76
|
const isLastPage = currentPage >= pageCount - 1;
|
|
80
77
|
|
|
81
78
|
return (
|
|
82
|
-
<div className="ogx-pagination" role="navigation" aria-label=
|
|
79
|
+
<div className="ogx-pagination" role="navigation" aria-label={locale.paginationLabel}>
|
|
83
80
|
{ }
|
|
84
81
|
<div className="ogx-pagination__page-size">
|
|
85
82
|
<label htmlFor={pageSizeId} className="ogx-pagination__label">
|
|
@@ -112,8 +109,8 @@ export function Pagination(props: PaginationProps) {
|
|
|
112
109
|
size="small"
|
|
113
110
|
onClick={handleFirstPage}
|
|
114
111
|
disabled={isFirstPage}
|
|
115
|
-
aria-label=
|
|
116
|
-
title=
|
|
112
|
+
aria-label={locale.paginationFirstPageLabel}
|
|
113
|
+
title={locale.paginationFirstPageTooltip}
|
|
117
114
|
>
|
|
118
115
|
<svg className="ogx-pagination__icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
119
116
|
<path d="M18.41 16.59L13.82 12l4.59-4.59L17 6l-6 6 6 6zM6 6h2v12H6z" />
|
|
@@ -126,8 +123,8 @@ export function Pagination(props: PaginationProps) {
|
|
|
126
123
|
size="small"
|
|
127
124
|
onClick={handlePreviousPage}
|
|
128
125
|
disabled={isFirstPage}
|
|
129
|
-
aria-label=
|
|
130
|
-
title=
|
|
126
|
+
aria-label={locale.paginationPreviousPageLabel}
|
|
127
|
+
title={locale.paginationPreviousPageTooltip}
|
|
131
128
|
>
|
|
132
129
|
<svg className="ogx-pagination__icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
133
130
|
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
|
|
@@ -143,8 +140,8 @@ export function Pagination(props: PaginationProps) {
|
|
|
143
140
|
size="small"
|
|
144
141
|
onClick={handleNextPage}
|
|
145
142
|
disabled={isLastPage}
|
|
146
|
-
aria-label=
|
|
147
|
-
title=
|
|
143
|
+
aria-label={locale.paginationNextPageLabel}
|
|
144
|
+
title={locale.paginationNextPageTooltip}
|
|
148
145
|
>
|
|
149
146
|
<svg className="ogx-pagination__icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
150
147
|
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
|
|
@@ -157,8 +154,8 @@ export function Pagination(props: PaginationProps) {
|
|
|
157
154
|
size="small"
|
|
158
155
|
onClick={handleLastPage}
|
|
159
156
|
disabled={isLastPage}
|
|
160
|
-
aria-label=
|
|
161
|
-
title=
|
|
157
|
+
aria-label={locale.paginationLastPageLabel}
|
|
158
|
+
title={locale.paginationLastPageTooltip}
|
|
162
159
|
>
|
|
163
160
|
<svg className="ogx-pagination__icon" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
164
161
|
<path d="M5.59 7.41L10.18 12l-4.59 4.59L7 18l6-6-6-6zM16 6h2v12h-2z" />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useState, useCallback } from 'react';
|
|
3
3
|
import { Input } from '../ui/Input';
|
|
4
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
4
5
|
|
|
5
6
|
export interface QuickFilterProps {
|
|
6
7
|
value?: string;
|
|
@@ -13,9 +14,11 @@ export function QuickFilter(props: QuickFilterProps) {
|
|
|
13
14
|
const {
|
|
14
15
|
value = '',
|
|
15
16
|
onChange,
|
|
16
|
-
placeholder
|
|
17
|
+
placeholder: placeholderProp,
|
|
17
18
|
debounceMs = 300
|
|
18
19
|
} = props;
|
|
20
|
+
const locale = useGridLocale();
|
|
21
|
+
const placeholder = placeholderProp ?? locale.toolbarSearchPlaceholder;
|
|
19
22
|
|
|
20
23
|
const [localValue, setLocalValue] = useState(value);
|
|
21
24
|
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
|
|
@@ -55,7 +58,7 @@ export function QuickFilter(props: QuickFilterProps) {
|
|
|
55
58
|
<button
|
|
56
59
|
className="ogx-quick-filter__clear"
|
|
57
60
|
onClick={handleClear}
|
|
58
|
-
aria-label=
|
|
61
|
+
aria-label={locale.toolbarSearchClear}
|
|
59
62
|
type="button"
|
|
60
63
|
>
|
|
61
64
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
@@ -5,6 +5,7 @@ import { Checkbox } from '../ui/Checkbox';
|
|
|
5
5
|
import { ExpandIcon } from '../ui/ExpandIcon';
|
|
6
6
|
import { DragHandleIcon } from '../ui/DragHandleIcon';
|
|
7
7
|
import { DetailPanel } from '../DetailPanel/DetailPanel';
|
|
8
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
8
9
|
import type { GridRowModel, GridColDef, GridRowId, GridColumnPinning, GridRowPinning, GridRowParams, GridCellParams, GridDetailPanelHeight, GridRowMeta } from '../../types';
|
|
9
10
|
import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
|
|
10
11
|
import { isColumnPinned, calculatePinnedPositions, getPinnedEdgeFields, isRowPinned } from '../../utils/pinning';
|
|
@@ -46,6 +47,8 @@ function mergeColSpan<R extends GridRowModel>(
|
|
|
46
47
|
|
|
47
48
|
export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
48
49
|
row: R;
|
|
50
|
+
/** A shimmer placeholder shown while infinite scroll loads more rows (`row` is unused). */
|
|
51
|
+
skeleton?: boolean;
|
|
49
52
|
/** The row's id (from getRowId). Defaults to `row.id`; the grid always passes it. */
|
|
50
53
|
rowId?: GridRowId;
|
|
51
54
|
columns: GridColDef<R>[];
|
|
@@ -106,8 +109,10 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
112
|
+
const locale = useGridLocale();
|
|
109
113
|
const {
|
|
110
114
|
row,
|
|
115
|
+
skeleton = false,
|
|
111
116
|
rowId: rowIdProp,
|
|
112
117
|
columns,
|
|
113
118
|
rowIndex,
|
|
@@ -197,7 +202,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
197
202
|
}, [onEditStop, id]);
|
|
198
203
|
|
|
199
204
|
// ── Skeleton row (shown during infinite-scroll fetch) ─────────────────────
|
|
200
|
-
if (
|
|
205
|
+
if (skeleton) {
|
|
201
206
|
// Render shimmer placeholders — one per visible column, matching row height
|
|
202
207
|
const cellCount = Math.max(columns.length, 5);
|
|
203
208
|
return (
|
|
@@ -294,7 +299,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
294
299
|
<div
|
|
295
300
|
className={`ogx__cell ogx__cell--drag-handle ${focusedCellField === '__reorder_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__reorder_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''}`}
|
|
296
301
|
role="gridcell"
|
|
297
|
-
aria-label={onDragStart ?
|
|
302
|
+
aria-label={onDragStart ? locale.rowReorderLabel : undefined}
|
|
298
303
|
aria-colindex={reorderColIndex}
|
|
299
304
|
data-field="__reorder_col__"
|
|
300
305
|
tabIndex={-1}
|
|
@@ -319,7 +324,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
319
324
|
} ${focusedCellField === '__expand_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__expand_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
|
|
320
325
|
}`}
|
|
321
326
|
role="gridcell"
|
|
322
|
-
aria-label={showDetailPanel ? (isDetailPanelExpanded ?
|
|
327
|
+
aria-label={showDetailPanel ? (isDetailPanelExpanded ? locale.rowDetailPanelCollapseLabel : locale.rowDetailPanelExpandLabel) : undefined}
|
|
323
328
|
aria-expanded={showDetailPanel ? isDetailPanelExpanded : undefined}
|
|
324
329
|
aria-controls={showDetailPanel && isDetailPanelExpanded ? detailPanelId : undefined}
|
|
325
330
|
aria-colindex={expandColIndex}
|
|
@@ -387,7 +392,7 @@ function RowImpl<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
|
|
|
387
392
|
checked={isSelected}
|
|
388
393
|
onChange={handleCheckboxChange}
|
|
389
394
|
onClick={(e) => e.stopPropagation()}
|
|
390
|
-
aria-label={isSelected ?
|
|
395
|
+
aria-label={isSelected ? locale.rowDeselectLabel(id) : locale.rowSelectLabel(id)}
|
|
391
396
|
tabIndex={-1}
|
|
392
397
|
onMouseDown={(e) => e.preventDefault()}
|
|
393
398
|
/>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
|
|
2
2
|
import { Input } from '../ui/Input';
|
|
3
|
+
import { useGridLocale } from '../DataGrid/gridLocaleContext';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
interface GlobalSearchProps {
|
|
@@ -32,7 +33,9 @@ function normalizeSearch(text: string): string {
|
|
|
32
33
|
return text.trim().split(/\s+/).filter(Boolean).join(' ');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export function GlobalSearch({ value = '', onChange, placeholder
|
|
36
|
+
export function GlobalSearch({ value = '', onChange, placeholder: placeholderProp, debounceMs = 250 }: GlobalSearchProps) {
|
|
37
|
+
const locale = useGridLocale();
|
|
38
|
+
const placeholder = placeholderProp ?? locale.toolbarSearchPlaceholder;
|
|
36
39
|
const [isExpanded, setIsExpanded] = useState(() => value.length > 0);
|
|
37
40
|
// Fully uncontrolled: localValue is the single source of truth.
|
|
38
41
|
const [localValue, setLocalValue] = useState(value);
|
|
@@ -152,11 +155,11 @@ export function GlobalSearch({ value = '', onChange, placeholder = 'Search...',
|
|
|
152
155
|
onFocus={handleFocus}
|
|
153
156
|
onBlur={handleBlur}
|
|
154
157
|
placeholder={placeholder}
|
|
155
|
-
aria-label=
|
|
158
|
+
aria-label={locale.toolbarSearchLabel}
|
|
156
159
|
autoComplete="off"
|
|
157
160
|
/>
|
|
158
161
|
{localValue && (
|
|
159
|
-
<button type="button" className="ogx-global-search__clear" onClick={handleClear} aria-label=
|
|
162
|
+
<button type="button" className="ogx-global-search__clear" onClick={handleClear} aria-label={locale.toolbarSearchClear}>
|
|
160
163
|
<CloseIcon />
|
|
161
164
|
</button>
|
|
162
165
|
)}
|