diff --git a/.eslintrc.json b/.eslintrc.json index 0295fd3d3..1853612f5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -28,7 +28,7 @@ "warn", { "patterns": [{ - "group": ["@mui/*/*", "!@mui/material/colors"], + "group": ["@mui/*/*", "!@mui/material/colors", "!@mui/material/locale"], "message": "Deep imports from MUI libraries are forbidden. Import only from the library root." }] } diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 83344a177..a0141f59c 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -26,10 +26,11 @@ import { Tooltip, Typography, } from '@mui/material'; +import { enUS, frFR } from '@mui/material/locale'; import { Comment as CommentIcon } from '@mui/icons-material'; import { BrowserRouter, useLocation, useMatch, useNavigate } from 'react-router'; import { IntlProvider, useIntl } from 'react-intl'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import translations from './demo_intl'; import PowsyblLogo from '../images/powsybl_logo.svg?react'; // eslint-disable-line import/no-unresolved import AppPackage from '../../package.json'; @@ -100,6 +101,8 @@ import { commonButtonFr, networkModificationsEn, networkModificationsFr, + logout, + equipmentStyles, } from '../../src'; const messages = { @@ -147,23 +150,23 @@ const messages = { }, }; -const lightTheme = createTheme({ +const lightTheme = { palette: { mode: 'light', }, -}); +}; -const darkTheme = createTheme({ +const darkTheme = { palette: { mode: 'dark', }, -}); +}; -const getMuiTheme = (theme) => { - if (theme === LIGHT_THEME) { - return lightTheme; - } - return darkTheme; +const useMuiTheme = (theme, language) => { + return useMemo( + () => createTheme(theme === LIGHT_THEME ? lightTheme : darkTheme, language === LANG_FRENCH ? frFR : enUS), + [language, theme] + ); }; const style = { @@ -845,7 +848,7 @@ function AppContent({ language, onLanguageClick }) { return ( - + diff --git a/package-lock.json b/package-lock.json index 14997b6ca..ddf31a646 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.94.0", "license": "MPL-2.0", "dependencies": { + "@ag-grid-community/locale": "^33.1.0", "@hello-pangea/dnd": "^18.0.1", "@react-querybuilder/dnd": "^8.2.0", "@react-querybuilder/material": "^8.2.0", @@ -129,6 +130,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@ag-grid-community/locale": { + "version": "33.1.1", + "resolved": "https://registry.npmjs.org/@ag-grid-community/locale/-/locale-33.1.1.tgz", + "integrity": "sha512-YElnMhPJqlPEWKMpwKkAAVL3Z2GeYwEVwFmXz4RW/GZHf7EZQcTREbFXFq9O3y2dYT9LfC7DG4eCjG/+oOI5Gw==", + "license": "MIT" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", diff --git a/package.json b/package.json index d3faeec9f..ff2ede4eb 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "licenses-check": "license-checker --summary --excludePrivatePackages --production --onlyAllow \"$( jq -r .onlyAllow[] license-checker-config.json | tr '\n' ';')\" --excludePackages \"$( jq -r .excludePackages[] license-checker-config.json | tr '\n' ';')\"" }, "dependencies": { + "@ag-grid-community/locale": "^33.1.0", "@hello-pangea/dnd": "^18.0.1", "@react-querybuilder/dnd": "^8.2.0", "@react-querybuilder/material": "^8.2.0", diff --git a/src/components/customAGGrid/customAggrid.style.ts b/src/components/customAGGrid/customAggrid.style.ts index 57190a1f5..08fe094a1 100644 --- a/src/components/customAGGrid/customAggrid.style.ts +++ b/src/components/customAGGrid/customAggrid.style.ts @@ -42,10 +42,4 @@ export const styles = { border: 'none !important', }, }), - noBorderRight: { - // hides right border for header of "Edit" column due to column being pinned - '& .ag-pinned-left-header': { - borderRight: 'none', - }, - }, } as const satisfies Record>; diff --git a/src/components/customAGGrid/customAggrid.tsx b/src/components/customAGGrid/customAggrid.tsx index 8eb79d0c1..8ea10cb8d 100644 --- a/src/components/customAGGrid/customAggrid.tsx +++ b/src/components/customAGGrid/customAggrid.tsx @@ -5,68 +5,73 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { useCallback } from 'react'; -import { AgGridReact, AgGridReactProps } from 'ag-grid-react'; -import { useIntl } from 'react-intl'; +import { forwardRef, useMemo } from 'react'; +import { AgGridReact, type AgGridReactProps } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; -import { ColumnResizedEvent, GetLocaleTextParams } from 'ag-grid-community'; -import { Box, type SxProps, type Theme, useTheme } from '@mui/material'; +import type { ColumnResizedEvent } from 'ag-grid-community'; +import { AG_GRID_LOCALE_EN, AG_GRID_LOCALE_FR } from '@ag-grid-community/locale'; +import { useIntl } from 'react-intl'; +import { Box, type BoxProps, type Theme, useTheme } from '@mui/material'; import { mergeSx } from '../../utils/styles'; import { CUSTOM_AGGRID_THEME, styles } from './customAggrid.style'; +import { type GsLangUser, LANG_ENGLISH, LANG_FRENCH } from '../../utils/langs'; + +export type AgGridLocale = Partial>; // using EN for keyof because it's the only who has more keys, so more complete +export type AgGridLocales = Record; -interface CustomAGGGridStyleProps { - shouldHidePinnedHeaderRightBorder?: boolean; +function useAgGridLocale(overrideLocales?: AgGridLocales) { + const intl = useIntl(); + return useMemo((): Record => { + switch ((intl.locale || intl.defaultLocale).toLowerCase().substring(0, 2)) { + case LANG_FRENCH: + return { + ...AG_GRID_LOCALE_FR, + thousandSeparator: ' ', + decimalSeparator: ',', + ...overrideLocales?.[LANG_FRENCH], + }; + case LANG_ENGLISH: + default: + return { ...AG_GRID_LOCALE_EN, ...overrideLocales?.[LANG_ENGLISH] }; + } + }, [intl.defaultLocale, intl.locale, overrideLocales]); } -export interface CustomAGGridProps extends AgGridReactProps, CustomAGGGridStyleProps {} +export type CustomAGGridProps = Omit, 'localeText' | 'getLocaleText'> & + Pick & { + overrideLocales?: AgGridLocales; + }; // We have to define a minWidth to column to activate this feature -const onColumnResized = (params: ColumnResizedEvent) => { - const { column, finished } = params; - const colDefinedMinWidth = column?.getColDef()?.minWidth; - if (column && colDefinedMinWidth && finished) { - const newWidth = column?.getActualWidth(); - if (newWidth < colDefinedMinWidth) { - params.api.setColumnWidths([{ key: column, newWidth: colDefinedMinWidth }], finished, params.source); +function onColumnResized({ api, column, finished, source }: ColumnResizedEvent) { + if (column) { + const colDefinedMinWidth = column.getColDef().minWidth; + if (colDefinedMinWidth && finished && column.getActualWidth() < colDefinedMinWidth) { + api.setColumnWidths([{ key: column, newWidth: colDefinedMinWidth }], finished, source); } } -}; - -export const CustomAGGrid = React.forwardRef((props, ref) => { - const { shouldHidePinnedHeaderRightBorder = false, ...agGridReactProps } = props; - const theme = useTheme(); - const intl = useIntl(); - - const GRID_PREFIX = 'grid.'; +} - const getLocaleText = useCallback( - (params: GetLocaleTextParams) => { - const key = GRID_PREFIX + params.key; - return intl.formatMessage({ - id: key, - defaultMessage: params.defaultValue, - }); - }, - [intl] - ); +export const CustomAGGrid = forwardRef( + ({ overrideLocales, sx, ...agGridReactProps }, ref) => { + const theme = useTheme(); - return ( - - - - ); -}); + return ( + + + + ); + } +); diff --git a/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx b/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx index 42bbabfde..8c0da0071 100644 --- a/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx +++ b/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx @@ -7,15 +7,11 @@ import { useCallback, useEffect, useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; -import { AgGridReactProps } from 'ag-grid-react'; -import 'ag-grid-community/styles/ag-grid.css'; -import 'ag-grid-community/styles/ag-theme-alpine.css'; import { Box, useTheme } from '@mui/material'; -import { useIntl } from 'react-intl'; -import { CellEditingStoppedEvent, ColumnState, SortChangedEvent } from 'ag-grid-community'; +import type { CellEditingStoppedEvent, ColumnState, SortChangedEvent } from 'ag-grid-community'; import { BottomRightButtons } from './BottomRightButtons'; import { FieldConstants } from '../../../../utils/constants/fieldConstants'; -import { CustomAGGrid } from '../../../customAGGrid'; +import { CustomAGGrid, type CustomAGGridProps } from '../../../customAGGrid'; const style = (customProps: any) => ({ grid: (theme: any) => ({ @@ -75,32 +71,30 @@ const style = (customProps: any) => ({ }), }); -export interface CustomAgGridTableProps { - name: string; - columnDefs: any; - makeDefaultRowData: any; - csvProps: unknown; - cssProps: unknown; - defaultColDef: unknown; - pagination: boolean; - paginationPageSize: number; - rowSelection?: AgGridReactProps['rowSelection']; - alwaysShowVerticalScroll: boolean; - stopEditingWhenCellsLoseFocus: boolean; -} +export type CustomAgGridTableProps = Required< + Pick< + CustomAGGridProps, + | 'columnDefs' + | 'defaultColDef' + | 'pagination' + | 'paginationPageSize' + | 'alwaysShowVerticalScroll' + | 'stopEditingWhenCellsLoseFocus' + > +> & + Pick & { + name: string; + makeDefaultRowData: any; + csvProps: unknown; + cssProps: unknown; + }; export function CustomAgGridTable({ name, - columnDefs, makeDefaultRowData, csvProps, cssProps, - defaultColDef, - pagination, - paginationPageSize, rowSelection, - alwaysShowVerticalScroll, - stopEditingWhenCellsLoseFocus, ...props }: Readonly) { // FIXME: right type => Theme --> not defined there ( gridStudy and gridExplore definition not the same ) @@ -182,15 +176,6 @@ export function CustomAgGridTable({ setNewRowAdded(true); }; - const intl = useIntl(); - const getLocaleText = useCallback( - (params: any) => { - const key = `agGrid.${params.key}`; - return intl.messages[key] || params.defaultValue; - }, - [intl] - ); - const onGridReady = (params: any) => { setGridApi(params); }; @@ -227,13 +212,11 @@ export function CustomAgGridTable({ move(getIndex(e.node.data), e.overIndex)} - columnDefs={columnDefs} detailRowAutoHeight onSelectionChanged={() => { setSelectedRows(gridApi.api.getSelectedRows()); @@ -242,10 +225,6 @@ export function CustomAgGridTable({ onCellEditingStopped={onCellEditingStopped} onSortChanged={onSortChanged} getRowId={(row) => row.data[FieldConstants.AG_GRID_ROW_UUID]} - pagination={pagination} - paginationPageSize={paginationPageSize} - alwaysShowVerticalScroll={alwaysShowVerticalScroll} - stopEditingWhenCellsLoseFocus={stopEditingWhenCellsLoseFocus} theme="legacy" {...props} /> diff --git a/src/components/topBar/TopBar.tsx b/src/components/topBar/TopBar.tsx index f23fc0559..f803595b9 100644 --- a/src/components/topBar/TopBar.tsx +++ b/src/components/topBar/TopBar.tsx @@ -49,14 +49,9 @@ import { LogoutProps } from '../authentication/Logout'; import { useStateBoolean } from '../../hooks/customStates/useStateBoolean'; import UserInformationDialog from './UserInformationDialog'; import UserSettingsDialog from './UserSettingsDialog'; -import { Metadata } from '../../utils'; -import { - DARK_THEME, - LANG_ENGLISH, - LANG_FRENCH, - LANG_SYSTEM, - LIGHT_THEME, -} from '../../utils/constants/browserConstants'; +import { type Metadata } from '../../utils/types/metadata'; +import { DARK_THEME, type GsTheme, LIGHT_THEME } from '../../utils/styles'; +import { type GsLang, LANG_ENGLISH, LANG_FRENCH, LANG_SYSTEM } from '../../utils/langs'; import MessageBanner from './MessageBanner'; const styles = { @@ -155,10 +150,6 @@ const CustomListItemIcon = styled(ListItemIcon)({ borderRadius: '25px', }); -export type GsLangUser = typeof LANG_ENGLISH | typeof LANG_FRENCH; -export type GsLang = GsLangUser | typeof LANG_SYSTEM; -export type GsTheme = typeof LIGHT_THEME | typeof DARK_THEME; - function abbreviationFromUserName(name: string) { const tab = name.split(' ').map((x) => x.charAt(0)); if (tab.length === 1) { diff --git a/src/components/topBar/tests/TopBar.test.tsx b/src/components/topBar/tests/TopBar.test.tsx index 2e5d9963e..5672e74f4 100644 --- a/src/components/topBar/tests/TopBar.test.tsx +++ b/src/components/topBar/tests/TopBar.test.tsx @@ -15,7 +15,7 @@ import { TopBar } from '../TopBar'; import { Metadata } from '../../..'; import PowsyblLogo from './powsybl_logo.svg?react'; -import { LANG_ENGLISH } from '../../../utils/constants/browserConstants'; +import { LANG_ENGLISH } from '../../../utils/langs'; import { topBarEn } from '../../../translations/en'; const apps: Metadata[] = [ diff --git a/src/hooks/useLocalizedCountries.ts b/src/hooks/useLocalizedCountries.ts index d47612373..436f49ad0 100644 --- a/src/hooks/useLocalizedCountries.ts +++ b/src/hooks/useLocalizedCountries.ts @@ -9,7 +9,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import localizedCountries, { LocalizedCountries } from 'localized-countries'; import countriesFr from 'localized-countries/data/fr'; import countriesEn from 'localized-countries/data/en'; -import { LANG_ENGLISH, LANG_FRENCH, LANG_SYSTEM } from '../utils/constants/browserConstants'; +import { LANG_ENGLISH, LANG_FRENCH, LANG_SYSTEM } from '../utils/langs'; const supportedLanguages = [LANG_FRENCH, LANG_ENGLISH]; diff --git a/src/utils/constants/index.ts b/src/utils/constants/index.ts index f1c893e9b..7b16738a3 100644 --- a/src/utils/constants/index.ts +++ b/src/utils/constants/index.ts @@ -4,7 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export * from './browserConstants'; export * from './fetchStatus'; export * from './fieldConstants'; export * from './uiConstants'; diff --git a/src/utils/index.ts b/src/utils/index.ts index dc79d96e7..6002bfdac 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -8,6 +8,7 @@ export * from './algos'; export * from './constants'; export * from './conversionUtils'; export * from './functions'; +export * from './langs'; export * from './mapper'; export * from './styles'; export * from './types'; diff --git a/src/utils/constants/browserConstants.ts b/src/utils/langs.ts similarity index 63% rename from src/utils/constants/browserConstants.ts rename to src/utils/langs.ts index 777033eb7..509bdb0c9 100644 --- a/src/utils/constants/browserConstants.ts +++ b/src/utils/langs.ts @@ -1,11 +1,12 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * Copyright © 2025, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export const DARK_THEME = 'Dark'; -export const LIGHT_THEME = 'Light'; + export const LANG_SYSTEM = 'sys'; export const LANG_ENGLISH = 'en'; export const LANG_FRENCH = 'fr'; +export type GsLangUser = typeof LANG_ENGLISH | typeof LANG_FRENCH; +export type GsLang = GsLangUser | typeof LANG_SYSTEM; diff --git a/src/utils/styles.ts b/src/utils/styles.ts index ed86480ab..eed7b121b 100644 --- a/src/utils/styles.ts +++ b/src/utils/styles.ts @@ -6,6 +6,10 @@ */ import { SxProps, Theme } from '@mui/material'; +export const DARK_THEME = 'Dark'; +export const LIGHT_THEME = 'Light'; +export type GsTheme = typeof LIGHT_THEME | typeof DARK_THEME; + // TODO do we need to export this to clients (index.ts) ? // like mui sx(slot)/class merging but simpler with less features // TODO use their system ? But it's named unstable_composeClasses so not supported? diff --git a/src/utils/types/equipmentType.ts b/src/utils/types/equipmentType.ts index 2c679beb6..0f884ea73 100644 --- a/src/utils/types/equipmentType.ts +++ b/src/utils/types/equipmentType.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { type Theme } from '@mui/material'; -import { LIGHT_THEME } from '../constants'; +import { LIGHT_THEME } from '../styles'; export const TYPE_TAG_MAX_SIZE = '90px'; export const VL_TAG_MAX_SIZE = '100px';