diff --git a/apps/docs/package-lock.json b/apps/docs/package-lock.json index 651f4fc9..de6b984b 100644 --- a/apps/docs/package-lock.json +++ b/apps/docs/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@astrojs/react": "^4.2.0", "@astrojs/tailwind": "^5.1.4", + "@shikijs/langs": "^4.1.0", "astro": "^5.7.0", "react": "^18.3.1", "react-data-table-component": "*", @@ -1989,12 +1990,28 @@ } }, "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.1.0.tgz", + "integrity": "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0" + "@shikijs/types": "4.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs/node_modules/@shikijs/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.1.0.tgz", + "integrity": "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/themes": { @@ -6143,6 +6160,15 @@ "@types/hast": "^3.0.4" } }, + "node_modules/shiki/node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/apps/docs/package.json b/apps/docs/package.json index 4dbaebb3..4217ad81 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -11,6 +11,7 @@ "dependencies": { "@astrojs/react": "^4.2.0", "@astrojs/tailwind": "^5.1.4", + "@shikijs/langs": "^4.1.0", "astro": "^5.7.0", "react": "^18.3.1", "react-data-table-component": "*", diff --git a/apps/docs/src/layouts/DocsLayout.astro b/apps/docs/src/layouts/DocsLayout.astro index b06e1655..57e88215 100644 --- a/apps/docs/src/layouts/DocsLayout.astro +++ b/apps/docs/src/layouts/DocsLayout.astro @@ -59,6 +59,7 @@ const nav = [ links: [ { label: 'Animations', href: '/docs/animations' }, { label: 'Accessibility', href: '/docs/accessibility' }, + { label: 'Localization', href: '/docs/localization' }, { label: 'RTL Support', href: '/docs/rtl' }, { label: 'Mobile', href: '/docs/mobile' }, ], diff --git a/apps/docs/src/pages/docs/api.md b/apps/docs/src/pages/docs/api.md index b1c518eb..eb2ad88f 100644 --- a/apps/docs/src/pages/docs/api.md +++ b/apps/docs/src/pages/docs/api.md @@ -165,6 +165,14 @@ Column-level footers live on each [`TableColumn`](#tablecolumnt) as the `foot | `onColumnOrderChange` | `(columns: TableColumn[]) => void` | - | Called after a drag-to-reorder column operation with the new column order. | | `onColumnGroupOrderChange` | `(groups: ColumnGroup[], columns: TableColumn[]) => void` | - | Called after a group drag-reorder with the new group order and the matching updated column order. | +### Localization + +| Prop | Type | Default | Description | +|---|---|---|---| +| `localization` | `Localization` | - | Override every user-visible string and aria-label in the table. Pass a pre-built locale or build your own. See [Localization](/docs/localization). | +| ~~`columnFilterOptions`~~ | `ColumnFilterOptions` | - | **Deprecated.** Use `localization={{ filter: { ... } }}` instead. Will be removed in v9. | +| ~~`expandableRowsOptions`~~ | `ExpandableRowsOptions` | - | **Deprecated.** Use `localization={{ expandable: { ... } }}` instead. Will be removed in v9. | + ## ColumnGroup Defines a spanning group header above one or more columns. Pass an array to the `columnGroups` prop. diff --git a/apps/docs/src/pages/docs/changelog.astro b/apps/docs/src/pages/docs/changelog.astro index 61d80de5..4f1a286c 100644 --- a/apps/docs/src/pages/docs/changelog.astro +++ b/apps/docs/src/pages/docs/changelog.astro @@ -11,6 +11,56 @@ import CodeBlock from '../../components/CodeBlock.astro'; repository on GitHub.

+

8.3.0

+ +

New features

+
    +
  • + Localization — new localization prop on DataTable + replaces the three separate option props (columnFilterOptions, + expandableRowsOptions, and pagination aria-label fields on + paginationComponentOptions). Pass a single object to translate every string and + aria-label in the table — filter panel, pagination navigation, and expand/collapse buttons. + → Localization docs +
  • +
  • + Built-in locales — import pre-built translations from the + react-data-table-component/locales subpath. Ships with: + English (en), French (fr), Spanish (es), + German (de), Brazilian Portuguese (ptBR), + Arabic — Modern Standard (ar), Egyptian (arEG), Levantine (arLV), + Hebrew (he), + Chinese Simplified (zhCN), Chinese Traditional (zhTW), + Japanese (ja), Korean (ko), Ukrainian (uk). + Each locale is individually tree-shakeable. +
  • +
  • + New utility exports: emptyFilterState(type) and isFilterActive(filter). + → Filtering docs +
  • +
+ +

Deprecations

+

The following will continue to work in 8.x but will be removed in v9. TypeScript will show a deprecation hint.

+
    +
  • + columnFilterOptions prop — use localization with a filter key instead. +
  • +
  • + expandableRowsOptions prop — use localization with an expandable key instead. +
  • +
  • + Pagination aria-label fields on paginationComponentOptions + (navigationAriaLabel, firstPageAriaLabel, previousPageAriaLabel, + nextPageAriaLabel, lastPageAriaLabel) — + use localization with a pagination key instead. +
  • +
  • + ColumnFilterOptions and ExpandableRowsOptions types — + use Localization['filter'] and Localization['expandable'] instead. +
  • +
+

8.2.0

New features

diff --git a/apps/docs/src/pages/docs/expandable.astro b/apps/docs/src/pages/docs/expandable.astro index 79015e14..32e0ea35 100644 --- a/apps/docs/src/pages/docs/expandable.astro +++ b/apps/docs/src/pages/docs/expandable.astro @@ -250,6 +250,43 @@ export default function App() { expandableInheritConditionalStylesbooleanApply the parent row's conditionalRowStyles to the expander row onRowExpandToggled(expanded: boolean, row: T) => voidFired when a row is expanded or collapsed animateRowsbooleanAnimate expand/collapse (respects prefers-reduced-motion) + localizationLocalizationLocalizable aria-labels for the expand/collapse button — see Localization below. + +

Localization

+

+ Override the aria-labels on the expand/collapse button via the localization prop. + Use a pre-built locale or supply your own — every key is optional. +

+ + `} /> + + `} /> diff --git a/apps/docs/src/pages/docs/filtering.astro b/apps/docs/src/pages/docs/filtering.astro index 92601677..e2f1b664 100644 --- a/apps/docs/src/pages/docs/filtering.astro +++ b/apps/docs/src/pages/docs/filtering.astro @@ -205,6 +205,46 @@ isFilterActive({ condition1: { operator: 'contains' } }); // false — isFilterActive({ condition1: { operator: 'contains', value: 'a' } }); // true isFilterActive({ condition1: { operator: 'blank' } }); // true — no value needed`} /> +

Localization

+

+ Use the localization prop to swap every string in the table UI. + Import a pre-built locale or build your own — all keys are optional and fall + back to English defaults. +

+ + `} /> + + `} /> + + `} /> +

Headless usage

Use useColumnFilter directly when building a custom table with the headless hooks. diff --git a/apps/docs/src/pages/docs/localization.astro b/apps/docs/src/pages/docs/localization.astro new file mode 100644 index 00000000..cfdcb1df --- /dev/null +++ b/apps/docs/src/pages/docs/localization.astro @@ -0,0 +1,176 @@ +--- +import DocsLayout from '../../layouts/DocsLayout.astro'; +import CodeBlock from '../../components/CodeBlock.astro'; +--- + + +

Localization

+ +

+ Pass a localization object to DataTable to override every + string and aria-label rendered by the table — filter panel, pagination navigation, and + expand/collapse buttons. All keys are optional and fall back to English defaults. +

+ +

Quick start

+

+ Import one of the pre-built locales from the /locales subpath and pass it as + localization. That's all you need for a fully translated table. +

+ + `} /> + +

Built-in locales

+ + + + + + + + + + + + + + + + + + + + + + + + +
ExportLanguage
enEnglish (default)
frFrench
esSpanish
deGerman
ptBRBrazilian Portuguese
arArabic (Modern Standard)
heHebrew
arEGArabic (Egyptian)
arLVArabic (Levantine)
zhCNChinese (Simplified)
zhTWChinese (Traditional)
jaJapanese
koKorean
ukUkrainian
+ +

+ Each locale is a standalone module. Only the locale you import enters your bundle — the + rest are tree-shaken away. +

+ +

Partial override

+

+ Spread an existing locale and replace only the keys you need. +

+ + `} /> + +

Custom locale from scratch

+

+ Build a locale object from the Localization type. Every key is optional — + any omitted key falls back to the English default. +

+ + `} /> + +

The Localization type

+

+ All three sub-objects are optional. Each key within them is also optional. +

+ + + +

Scope

+

+ localization covers strings rendered by the table's built-in UI. Visible text + in the pagination bar (rowsPerPageText, rangeSeparatorText, + selectAllRowsItemText) is still controlled by + paginationComponentOptions, since those affect layout as well as text. +

+ diff --git a/apps/docs/src/pages/docs/pagination.astro b/apps/docs/src/pages/docs/pagination.astro index d4713bc8..b806d08a 100644 --- a/apps/docs/src/pages/docs/pagination.astro +++ b/apps/docs/src/pages/docs/pagination.astro @@ -572,7 +572,7 @@ function MyPagination({ rowsPerPage, rowCount, currentPage, onChangePage }: Pagi paginationComponentOptions PaginationOptions - - Options forwarded to the default pagination component (label customisation, etc.). + Options forwarded to the default pagination component. Covers visible text (rowsPerPageText, rangeSeparatorText, selectAllRowsItemText). Navigation aria-labels are set via the localization prop. paginationIconFirstPage @@ -612,4 +612,48 @@ function MyPagination({ rowsPerPage, rowCount, currentPage, onChangePage }: Pagi + +

Localization

+

+ All navigation aria-labels on the built-in pagination bar are controlled via the + localization prop. Use a pre-built locale or supply your own — every key is + optional and falls back to the English default. Visible text such as + rowsPerPageText and rangeSeparatorText is still set via + paginationComponentOptions. +

+ + `} /> + + `} /> + + `} /> diff --git a/apps/docs/src/pages/docs/rtl.astro b/apps/docs/src/pages/docs/rtl.astro index 83258e04..5a891f4b 100644 --- a/apps/docs/src/pages/docs/rtl.astro +++ b/apps/docs/src/pages/docs/rtl.astro @@ -84,6 +84,36 @@ import RTLDemo from '../../components/demos/RTLDemo.tsx';
  • Column right: true alignment continues to work as expected. Align numbers relative to the reading direction of the cell content, not the page.
  • +

    Pairing with localization

    +

    + direction controls layout; localization controls strings. For a + fully translated RTL table, use both together. The library keeps them separate on purpose — + you may want French strings without changing layout direction, or an Arabic locale on a + mixed-direction page where you manage dir yourself. +

    + + `} /> + +

    + If your app already sets <html dir="rtl">, use Direction.AUTO + so the table follows the page without an explicit prop: +

    + + `} /> +

    Prop reference

    diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index 7e633708..f01ba595 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -4,7 +4,8 @@ "jsx": "react-jsx", "jsxImportSource": "react", "paths": { - "react-data-table-component": ["../../src/index.ts"] + "react-data-table-component": ["../../src/index.ts"], + "react-data-table-component/locales": ["../../src/locales/index.ts"] } } } diff --git a/package-lock.json b/package-lock.json index 2f1bb0f5..f985a324 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-data-table-component", - "version": "8.0.1", + "version": "8.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-data-table-component", - "version": "8.0.1", + "version": "8.2.0", "license": "Apache-2.0", "devDependencies": { "@testing-library/dom": "^10.4.1", @@ -14,9 +14,9 @@ "@testing-library/react": "^16.3.2", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", - "@typescript-eslint/eslint-plugin": "^8.59.2", - "@typescript-eslint/parser": "^8.59.2", - "@vitest/coverage-v8": "^4.1.6", + "@typescript-eslint/eslint-plugin": "^8.59.4", + "@typescript-eslint/parser": "^8.59.4", + "@vitest/coverage-v8": "^4.1.7", "concurrently": "^9.2.1", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", @@ -30,7 +30,7 @@ "react-dom": "^18.3.1", "tsup": "^8.5.1", "typescript": "^6.0.3", - "vitest": "^4.1.6" + "vitest": "^4.1.7" }, "peerDependencies": { "react": ">=18.0.0", @@ -2195,17 +2195,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", - "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", + "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/type-utils": "8.59.3", - "@typescript-eslint/utils": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/type-utils": "8.59.4", + "@typescript-eslint/utils": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2218,7 +2218,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.3", + "@typescript-eslint/parser": "^8.59.4", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2234,16 +2234,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", - "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz", + "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", "debug": "^4.4.3" }, "engines": { @@ -2259,14 +2259,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", - "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz", + "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.3", - "@typescript-eslint/types": "^8.59.3", + "@typescript-eslint/tsconfig-utils": "^8.59.4", + "@typescript-eslint/types": "^8.59.4", "debug": "^4.4.3" }, "engines": { @@ -2281,14 +2281,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", - "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz", + "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3" + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2299,9 +2299,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", - "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz", + "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==", "dev": true, "license": "MIT", "engines": { @@ -2316,15 +2316,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", - "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz", + "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3", - "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/utils": "8.59.4", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2341,9 +2341,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", - "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", + "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", "dev": true, "license": "MIT", "engines": { @@ -2355,16 +2355,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", - "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz", + "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.3", - "@typescript-eslint/tsconfig-utils": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/visitor-keys": "8.59.3", + "@typescript-eslint/project-service": "8.59.4", + "@typescript-eslint/tsconfig-utils": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2383,16 +2383,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", - "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz", + "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.3", - "@typescript-eslint/types": "8.59.3", - "@typescript-eslint/typescript-estree": "8.59.3" + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2407,13 +2407,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", - "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz", + "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/types": "8.59.4", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2438,14 +2438,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz", - "integrity": "sha512-36l628fQ/9a/8ihy97eOtEnvWQEdqULQOJtcaxtoNq0G1w3Mxd4szSahOaMM9/NGyZ+hyKcMtIW/WIxq0XQViQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", + "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.6", + "@vitest/utils": "4.1.7", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -2459,8 +2459,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.6", - "vitest": "4.1.6" + "@vitest/browser": "4.1.7", + "vitest": "4.1.7" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2479,16 +2479,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", - "integrity": "sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.6", - "@vitest/utils": "4.1.6", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2496,10 +2496,37 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "node_modules/@vitest/pretty-format": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.6.tgz", - "integrity": "sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", "dev": true, "license": "MIT", "dependencies": { @@ -2510,13 +2537,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.6.tgz", - "integrity": "sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.6", + "@vitest/utils": "4.1.7", "pathe": "^2.0.3" }, "funding": { @@ -2524,14 +2551,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.6.tgz", - "integrity": "sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.6", - "@vitest/utils": "4.1.6", + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2540,9 +2567,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.6.tgz", - "integrity": "sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", "dev": true, "license": "MIT", "funding": { @@ -2550,13 +2577,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.6.tgz", - "integrity": "sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.6", + "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3575,6 +3602,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -6129,6 +6163,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -7115,19 +7162,6 @@ } } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", @@ -8038,20 +8072,98 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.1", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/vitest": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.6.tgz", - "integrity": "sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.6", - "@vitest/mocker": "4.1.6", - "@vitest/pretty-format": "4.1.6", - "@vitest/runner": "4.1.6", - "@vitest/snapshot": "4.1.6", - "@vitest/spy": "4.1.6", - "@vitest/utils": "4.1.6", + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -8079,12 +8191,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.6", - "@vitest/browser-preview": "4.1.6", - "@vitest/browser-webdriverio": "4.1.6", - "@vitest/coverage-istanbul": "4.1.6", - "@vitest/coverage-v8": "4.1.6", - "@vitest/ui": "4.1.6", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -8128,53 +8240,6 @@ } } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.6.tgz", - "integrity": "sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.6", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest/node_modules/tinyexec": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", @@ -8185,84 +8250,6 @@ "node": ">=18" } }, - "node_modules/vitest/node_modules/vite": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.1", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index ff3c042c..b2e61b89 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,11 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./locales": { + "types": "./dist/locales/index.d.ts", + "import": "./dist/locales/index.mjs", + "require": "./dist/locales/index.js" + }, "./css": "./dist/DataTable.css" }, "files": [ @@ -50,9 +55,9 @@ "@testing-library/react": "^16.3.2", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", - "@typescript-eslint/eslint-plugin": "^8.59.2", - "@typescript-eslint/parser": "^8.59.2", - "@vitest/coverage-v8": "^4.1.6", + "@typescript-eslint/eslint-plugin": "^8.59.4", + "@typescript-eslint/parser": "^8.59.4", + "@vitest/coverage-v8": "^4.1.7", "concurrently": "^9.2.1", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", @@ -66,7 +71,7 @@ "react-dom": "^18.3.1", "tsup": "^8.5.1", "typescript": "^6.0.3", - "vitest": "^4.1.6" + "vitest": "^4.1.7" }, "peerDependencies": { "react": ">=18.0.0", diff --git a/src/__tests__/ColumnFilter.test.tsx b/src/__tests__/ColumnFilter.test.tsx index 12b1d836..2ec5a373 100644 --- a/src/__tests__/ColumnFilter.test.tsx +++ b/src/__tests__/ColumnFilter.test.tsx @@ -244,3 +244,126 @@ describe('ColumnFilter:date filter type', () => { expect(input.type).toBe('date'); }); }); + +describe('ColumnFilter:localization (options prop)', () => { + const options = { + filterColumnAriaLabel: 'test-filter-col', + filterActiveAriaLabel: 'test-filter-active', + filterPanelAriaLabel: 'test-filter-panel', + operatorAriaLabel: 'test-operator', + valuePlaceholder: 'test-placeholder', + valueAriaLabel: 'test-value', + value2AriaLabel: 'test-value2', + value2Placeholder: 'test-placeholder2', + betweenSeparatorText: 'test-sep', + removeConditionAriaLabel: 'test-remove', + addConditionAriaLabel: 'test-add', + addConditionLabel: 'test-add-label', + clearLabel: 'test-clear', + applyLabel: 'test-apply', + andLabel: 'test-and', + orLabel: 'test-or', + operators: { contains: 'test-contains', equals: 'test-equals' }, + }; + + test('filter icon button uses custom aria-label', () => { + const { container } = setup({ options }); + const btn = container.querySelector('button') as HTMLButtonElement; + expect(btn.getAttribute('aria-label')).toBe('test-filter-col'); + }); + + test('active filter uses custom active aria-label', () => { + const { container } = setup({ + options, + filterValue: { condition1: { operator: 'contains', value: 'x' } }, + }); + const btn = container.querySelector('button') as HTMLButtonElement; + expect(btn.getAttribute('aria-label')).toBe('test-filter-active'); + }); + + function openPanelByClass(container: HTMLElement) { + const btn = container.querySelector('button.rdt_filterIcon') as HTMLElement; + fireEvent.click(btn); + } + + test('panel uses custom aria-label', () => { + const { container } = setup({ options }); + openPanelByClass(container); + expect(container.querySelector('[role="dialog"]')?.getAttribute('aria-label')).toBe('test-filter-panel'); + }); + + test('operator select uses custom aria-label', () => { + const { container } = setup({ options }); + openPanelByClass(container); + expect(container.querySelector('select')?.getAttribute('aria-label')).toBe('test-operator'); + }); + + test('value input uses custom placeholder and aria-label', () => { + const { container } = setup({ options }); + openPanelByClass(container); + const input = container.querySelector('input') as HTMLInputElement; + expect(input.getAttribute('aria-label')).toBe('test-value'); + expect(input.placeholder).toBe('test-placeholder'); + }); + + test('custom operator labels appear in select; untranslated keys fall back to default', () => { + const { container } = setup({ options }); + openPanelByClass(container); + const select = container.querySelector('select') as HTMLSelectElement; + const optionTexts = Array.from(select.options).map(o => o.text); + expect(optionTexts).toContain('test-contains'); + expect(optionTexts).toContain('test-equals'); + expect(optionTexts).toContain('Does not contain'); // untranslated key falls back to English default + }); + + test('Apply and Clear buttons use custom labels', () => { + const { container } = setup({ options }); + openPanelByClass(container); + const btns = Array.from(container.querySelectorAll('.rdt_filterActions button')).map(b => b.textContent); + expect(btns).toContain('test-clear'); + expect(btns).toContain('test-apply'); + }); + + test('add-condition button uses custom label and aria-label', () => { + const { container } = setup({ options }); + openPanelByClass(container); + const addBtn = container.querySelector('.rdt_filterAddCondition') as HTMLButtonElement; + expect(addBtn.textContent).toBe('test-add-label'); + expect(addBtn.getAttribute('aria-label')).toBe('test-add'); + }); + + test('AND / OR buttons use custom labels', () => { + const { container } = setup({ options }); + openPanelByClass(container); + fireEvent.click(container.querySelector('.rdt_filterAddCondition') as HTMLElement); + const logicBtns = Array.from(container.querySelectorAll('.rdt_filterLogicBtn')).map(b => b.textContent); + expect(logicBtns).toContain('test-and'); + expect(logicBtns).toContain('test-or'); + }); + + test('remove-condition button uses custom aria-label', () => { + const { container } = setup({ options }); + openPanelByClass(container); + fireEvent.click(container.querySelector('.rdt_filterAddCondition') as HTMLElement); + const removeBtn = container.querySelector('.rdt_filterRemoveBtn') as HTMLButtonElement; + expect(removeBtn.getAttribute('aria-label')).toBe('test-remove'); + }); + + test('between separator uses custom text', () => { + const { container } = setup({ options, filterType: 'number', filterValue: emptyFilterState('number') }); + openPanelByClass(container); + const select = container.querySelector('select') as HTMLSelectElement; + fireEvent.change(select, { target: { value: 'between' } }); + expect(container.querySelector('.rdt_filterBetweenSep')?.textContent).toBe('test-sep'); + }); + + test('second value input uses custom aria-label and placeholder', () => { + const { container } = setup({ options, filterType: 'number', filterValue: emptyFilterState('number') }); + openPanelByClass(container); + const select = container.querySelector('select') as HTMLSelectElement; + fireEvent.change(select, { target: { value: 'between' } }); + const input2 = container.querySelector('input[aria-label="test-value2"]') as HTMLInputElement; + expect(input2).not.toBeNull(); + expect(input2.placeholder).toBe('test-placeholder2'); + }); +}); diff --git a/src/__tests__/Pagination.test.tsx b/src/__tests__/Pagination.test.tsx index 26cbc74f..c1507253 100644 --- a/src/__tests__/Pagination.test.tsx +++ b/src/__tests__/Pagination.test.tsx @@ -204,6 +204,86 @@ describe('when changing rows per page', () => { }); }); +describe('Pagination:localization', () => { + const localization = { + navigationAriaLabel: 'test-nav', + firstPageAriaLabel: 'test-first', + previousPageAriaLabel: 'test-prev', + nextPageAriaLabel: 'test-next', + lastPageAriaLabel: 'test-last', + }; + + test('navigation nav uses custom navigationAriaLabel', () => { + const { container } = renderWithTheme( + , + ); + expect(container.querySelector('nav')?.getAttribute('aria-label')).toBe('test-nav'); + }); + + test('first-page button uses custom firstPageAriaLabel', () => { + const { container } = renderWithTheme( + , + ); + expect(container.querySelector('#pagination-first-page')?.getAttribute('aria-label')).toBe('test-first'); + }); + + test('previous-page button uses custom previousPageAriaLabel', () => { + const { container } = renderWithTheme( + , + ); + expect(container.querySelector('#pagination-previous-page')?.getAttribute('aria-label')).toBe('test-prev'); + }); + + test('next-page button uses custom nextPageAriaLabel', () => { + const { container } = renderWithTheme( + , + ); + expect(container.querySelector('#pagination-next-page')?.getAttribute('aria-label')).toBe('test-next'); + }); + + test('last-page button uses custom lastPageAriaLabel', () => { + const { container } = renderWithTheme( + , + ); + expect(container.querySelector('#pagination-last-page')?.getAttribute('aria-label')).toBe('test-last'); + }); +}); + describe('when the screensize is small', () => { test('paginationComponentOption noRowsPerPage should be respected', () => { globalThis.innerWidth = 500; diff --git a/src/components/ColumnFilter.tsx b/src/components/ColumnFilter.tsx index 63745390..bfecad29 100644 --- a/src/components/ColumnFilter.tsx +++ b/src/components/ColumnFilter.tsx @@ -1,11 +1,13 @@ import * as React from 'react'; import '../DataTable.css'; -import type { FilterState, FilterCondition, FilterOperator, FilterType } from '../types'; +import type { FilterState, FilterCondition, FilterOperator, FilterType, Localization } from '../types'; + +type ColumnFilterOptions = NonNullable; import { emptyFilterState, isFilterActive } from '../hooks/useColumnFilter'; type OperatorOption = { value: FilterOperator; label: string; noInput?: boolean; twoInputs?: boolean }; -const TEXT_OPERATORS: OperatorOption[] = [ +const DEFAULT_TEXT_OPERATORS: OperatorOption[] = [ { value: 'contains', label: 'Contains' }, { value: 'notContains', label: 'Does not contain' }, { value: 'equals', label: 'Equals' }, @@ -16,7 +18,7 @@ const TEXT_OPERATORS: OperatorOption[] = [ { value: 'notBlank', label: 'Not blank', noInput: true }, ]; -const NUMBER_OPERATORS: OperatorOption[] = [ +const DEFAULT_NUMBER_OPERATORS: OperatorOption[] = [ { value: 'equals', label: 'Equals' }, { value: 'notEquals', label: 'Does not equal' }, { value: 'gt', label: 'Greater than' }, @@ -28,7 +30,7 @@ const NUMBER_OPERATORS: OperatorOption[] = [ { value: 'notBlank', label: 'Not blank', noInput: true }, ]; -const DATE_OPERATORS: OperatorOption[] = [ +const DEFAULT_DATE_OPERATORS: OperatorOption[] = [ { value: 'equals', label: 'Equals' }, { value: 'before', label: 'Before' }, { value: 'after', label: 'After' }, @@ -37,10 +39,15 @@ const DATE_OPERATORS: OperatorOption[] = [ { value: 'notBlank', label: 'Not blank', noInput: true }, ]; -function operatorsFor(filterType: FilterType): OperatorOption[] { - if (filterType === 'number') return NUMBER_OPERATORS; - if (filterType === 'date') return DATE_OPERATORS; - return TEXT_OPERATORS; +function operatorsFor(filterType: FilterType, overrides?: ColumnFilterOptions['operators']): OperatorOption[] { + const base = + filterType === 'number' + ? DEFAULT_NUMBER_OPERATORS + : filterType === 'date' + ? DEFAULT_DATE_OPERATORS + : DEFAULT_TEXT_OPERATORS; + if (!overrides) return base; + return base.map(op => (overrides[op.value] ? { ...op, label: overrides[op.value]! } : op)); } function defaultOperator(filterType: FilterType): FilterOperator { @@ -54,12 +61,13 @@ function emptyCondition(filterType: FilterType): FilterCondition { type ConditionRowProps = { condition: FilterCondition; filterType: FilterType; + options: ColumnFilterOptions; onChange: (next: FilterCondition) => void; onRemove?: () => void; }; -function ConditionRow({ condition, filterType, onChange, onRemove }: ConditionRowProps): JSX.Element { - const operators = operatorsFor(filterType); +function ConditionRow({ condition, filterType, options, onChange, onRemove }: ConditionRowProps): JSX.Element { + const operators = operatorsFor(filterType, options.operators); const selected = operators.find(o => o.value === condition.operator) ?? operators[0]; const inputType = filterType === 'number' ? 'number' : filterType === 'date' ? 'date' : 'text'; @@ -69,7 +77,7 @@ function ConditionRow({ condition, filterType, onChange, onRemove }: ConditionRo className="rdt_filterSelect" value={condition.operator} onChange={e => onChange({ operator: e.target.value as FilterOperator })} - aria-label="Filter operator" + aria-label={options.operatorAriaLabel ?? 'Filter operator'} > {operators.map(op => (