@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/ai.cjs.map +1 -1
  3. package/dist/ai.es.js.map +1 -1
  4. package/dist/index.d.ts +342 -3
  5. package/dist/opengridx.css +1 -1
  6. package/dist/opengridx.es.js +6952 -6697
  7. package/dist/opengridx.es.js.map +1 -1
  8. package/dist/opengridx.umd.js +17 -17
  9. package/dist/opengridx.umd.js.map +1 -1
  10. package/docs/API_REFERENCE.md +121 -4
  11. package/docs/customization/theming.md +11 -2
  12. package/docs/features/ai-toolkit.md +1 -1
  13. package/lib/components/AiAssistant/GridAiAssistantPanel.css +2 -2
  14. package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +16 -14
  15. package/lib/components/Cell/Cell.css +1 -3
  16. package/lib/components/Cell/CellErrorBoundary.tsx +17 -10
  17. package/lib/components/CellSelectionStats/CellSelectionStats.tsx +5 -3
  18. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +1 -2
  19. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +3 -1
  20. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +6 -4
  21. package/lib/components/DataGrid/DataGrid.css +1 -28
  22. package/lib/components/DataGrid/GridAggregationFooter.tsx +3 -1
  23. package/lib/components/DataGrid/GridErrorOverlay.tsx +4 -2
  24. package/lib/components/DataGrid/GridListView.tsx +7 -5
  25. package/lib/components/DataGrid/GridLiveRegion.tsx +5 -3
  26. package/lib/components/DataGrid/GridLoadingOverlay.tsx +3 -1
  27. package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
  28. package/lib/components/DataGrid/GridRoot.tsx +8 -1
  29. package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +3 -1
  30. package/lib/components/DataGrid/GridViewport.tsx +3 -1
  31. package/lib/components/DataGrid/GridVirtualRows.tsx +3 -2
  32. package/lib/components/DataGrid/gridLocaleContext.ts +13 -0
  33. package/lib/components/FilterPanel/FilterPanel.tsx +25 -56
  34. package/lib/components/Header/ColumnMenu.css +5 -5
  35. package/lib/components/Header/ColumnMenu.tsx +17 -15
  36. package/lib/components/Header/Header.css +2 -11
  37. package/lib/components/Header/Header.tsx +8 -6
  38. package/lib/components/HeaderFilter/HeaderFilterCell.tsx +6 -37
  39. package/lib/components/HeaderFilter/HeaderFilterRow.tsx +3 -1
  40. package/lib/components/ListView/ListView.css +0 -16
  41. package/lib/components/ListView/ListViewRow.tsx +3 -1
  42. package/lib/components/Pagination/Pagination.tsx +15 -18
  43. package/lib/components/QuickFilter/QuickFilter.tsx +5 -2
  44. package/lib/components/Row/Row.tsx +9 -4
  45. package/lib/components/Toolbar/GlobalSearch.tsx +6 -3
  46. package/lib/components/Toolbar/GridToolbar.tsx +35 -29
  47. package/lib/components/Toolbar/PivotPanel.tsx +26 -24
  48. package/lib/components/ui/Checkbox.css +5 -5
  49. package/lib/components/ui/DragHandleIcon.tsx +3 -1
  50. package/lib/components/ui/ExpandIcon.css +3 -3
  51. package/lib/components/ui/ExpandIcon.tsx +3 -1
  52. package/lib/hooks/core/useGridRowInteractions.ts +13 -3
  53. package/lib/hooks/core/useGridToolbarStack.ts +2 -2
  54. package/lib/hooks/core/useGridVirtualization.ts +40 -25
  55. package/lib/hooks/core/useShallowStableValue.ts +29 -0
  56. package/lib/hooks/features/useGridAiAssistant.ts +52 -22
  57. package/lib/hooks/features/useGridSpanning.ts +1 -1
  58. package/lib/styles/base/reset.css +6 -7
  59. package/lib/theme/DataGridThemeProvider.tsx +9 -0
  60. package/lib/theme/__baseline__/default-theme.darwin.chromium.json +1 -1503
  61. package/lib/theme/__baseline__/default-theme.darwin.firefox.json +1 -1503
  62. package/lib/theme/__baseline__/default-theme.darwin.webkit.json +1 -1503
  63. package/lib/theme/types.ts +18 -0
  64. package/lib/types/locale.ts +344 -0
  65. package/lib/types/rows.ts +0 -2
  66. package/lib/utils/cellSelection.ts +0 -2
  67. package/lib/utils/editing/index.ts +1 -1
  68. package/lib/utils/filtering/filterInput.ts +45 -0
  69. package/lib/utils/filtering/index.ts +0 -28
  70. package/lib/utils/gridLocale.ts +241 -0
  71. package/lib/utils/pinning/index.ts +0 -107
  72. package/lib/utils/sorting/index.ts +42 -33
  73. package/lib/utils/values.ts +5 -1
  74. package/llms.txt +9 -3
  75. package/package.json +1 -1
  76. 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="Row reorder handle"
353
+ aria-label={locale.headerRowReorderLabel}
352
354
  aria-colindex={reorderColIndex}
353
355
  data-field="__reorder_col__"
354
356
  tabIndex={-1}
355
- title="Drag to reorder rows"
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="Expand row details"
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 ? 'Deselect all rows' : 'Select all rows'}
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
- ? `Sorted ${sortDirection === 'asc' ? 'ascending' : 'descending'}, sort priority ${sortPosition + 1} of ${sortModel.length}`
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={`Open column menu for ${colDef.headerName || colDef.field}`}
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 && sameValue(a.value, b.value);
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 = getValueOptions(colDef);
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, fromInputText(text, 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 ? fromInputText(draft, op) : item?.value;
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 => matchesOption(o, item?.value));
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={`No filter for ${colDef.headerName || colDef.field}`}
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={`Select row ${id}`}
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
- const displayedRows = rowCount === 0 ? 0 : `${firstRowIndex + 1}–${lastRowIndex}`;
47
-
48
- const rowsPerPageLabel = localeText?.paginationRowsPerPage ?? 'Rows per page:';
49
- const ofLabel = localeText?.paginationOf
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="Pagination">
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="Go to first page"
116
- title="First page"
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="Go to previous page"
130
- title="Previous page"
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="Go to next page"
147
- title="Next page"
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="Go to last page"
161
- title="Last page"
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 = 'Search...',
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="Clear search"
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 (row._isSkeleton) {
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 ? 'Drag to reorder row' : undefined}
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 ? 'Collapse row details' : 'Expand row details') : undefined}
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 ? `Deselect row ${id}` : `Select row ${id}`}
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 = 'Search...', debounceMs = 250 }: GlobalSearchProps) {
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="Global Search"
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="Clear search">
162
+ <button type="button" className="ogx-global-search__clear" onClick={handleClear} aria-label={locale.toolbarSearchClear}>
160
163
  <CloseIcon />
161
164
  </button>
162
165
  )}