From 527cc9f0f77c8d9bbda54863b4729407a0006271 Mon Sep 17 00:00:00 2001 From: vargastat Date: Wed, 18 Dec 2024 14:19:06 +0100 Subject: [PATCH 1/4] feat: StdModal and StdModalWrapper added --- .../common/layout/stdModal/StdModal.tsx | 53 ++++++++++++++ .../common/layout/stdModal/StdModalOpener.tsx | 22 ++++++ .../layout/stdModal/modalClassBuilder.ts | 21 ++++++ .../slots/StdModalTitle/StdModalTitle.tsx | 64 ++++++++++++++++ .../StdModalTitle/modalTitleClassBuilder.ts | 30 ++++++++ .../tests/modalTitleClassBuilder.test.ts | 32 ++++++++ .../tests/stdModalTitle.test.tsx | 30 ++++++++ .../stdModalContents/StdModalContent.tsx | 11 +++ .../StdModalLateralContent.tsx | 11 +++ .../tests/stdModalContent.test.tsx | 18 +++++ .../tests/stdModalLateralContent.test.tsx | 18 +++++ .../slots/stdModalFooter/StdModalFooter.tsx | 37 ++++++++++ .../stdModalFooter/modalFooterClassBuilder.ts | 19 +++++ .../tests/modalFooterClassBuilder.test.ts | 16 ++++ .../tests/stdModalFooter.test.tsx | 56 ++++++++++++++ .../stdModalWrapper/StdModalWrapper.tsx | 73 +++++++++++++++++++ .../stdModalWrapper/modalClassBuilder.ts | 20 +++++ .../tests/modalWrapperClassBuilder.test.ts | 20 +++++ .../tests/stdModalWrapper.test.tsx | 18 +++++ .../layout/stdModal/tests/stdModal.test.tsx | 58 +++++++++++++++ .../stdModal/tests/stdModalOpener.test.tsx | 51 +++++++++++++ src/shared/utils/slotsUtils.ts | 15 ++++ 22 files changed, 693 insertions(+) create mode 100644 src/components/common/layout/stdModal/StdModal.tsx create mode 100644 src/components/common/layout/stdModal/StdModalOpener.tsx create mode 100644 src/components/common/layout/stdModal/modalClassBuilder.ts create mode 100644 src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx create mode 100644 src/components/common/layout/stdModal/slots/StdModalTitle/modalTitleClassBuilder.ts create mode 100644 src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts create mode 100644 src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx create mode 100644 src/components/common/layout/stdModal/slots/stdModalContents/StdModalContent.tsx create mode 100644 src/components/common/layout/stdModal/slots/stdModalContents/StdModalLateralContent.tsx create mode 100644 src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx create mode 100644 src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx create mode 100644 src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx create mode 100644 src/components/common/layout/stdModal/slots/stdModalFooter/modalFooterClassBuilder.ts create mode 100644 src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts create mode 100644 src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx create mode 100644 src/components/common/layout/stdModal/stdModalWrapper/StdModalWrapper.tsx create mode 100644 src/components/common/layout/stdModal/stdModalWrapper/modalClassBuilder.ts create mode 100644 src/components/common/layout/stdModal/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts create mode 100644 src/components/common/layout/stdModal/stdModalWrapper/tests/stdModalWrapper.test.tsx create mode 100644 src/components/common/layout/stdModal/tests/stdModal.test.tsx create mode 100644 src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx create mode 100644 src/shared/utils/slotsUtils.ts diff --git a/src/components/common/layout/stdModal/StdModal.tsx b/src/components/common/layout/stdModal/StdModal.tsx new file mode 100644 index 0000000..3ae20ff --- /dev/null +++ b/src/components/common/layout/stdModal/StdModal.tsx @@ -0,0 +1,53 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { useStdId } from '@/hooks/useStdId'; +import { findSlotOfType } from '@/shared/utils/slotsUtils'; +import { ReactElement } from 'react'; +import { modalClassBuilder } from './modalClassBuilder'; +import StdModalTitle from './slots/StdModalTitle/StdModalTitle'; +import StdModalContent from './slots/stdModalContents/StdModalContent'; +import StdModalLateralContent from './slots/stdModalContents/StdModalLateralContent'; +import StdModalFooter from './slots/stdModalFooter/StdModalFooter'; +import StdModalWrapper, { StdModalProps } from './stdModalWrapper/StdModalWrapper'; + +export type ModalSize = 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge'; + +export type StdModalComponentProps = StdModalProps & { + children?: ReactElement | ReactElement[]; + size?: ModalSize; + id?: string; +}; + +function StdModalComponent({ onClose, size, children = undefined, id: propsId }: StdModalComponentProps) { + const id = useStdId('modal', propsId); + const TitleComponent = findSlotOfType(children, StdModalTitle); + const ContentComponent = findSlotOfType(children, StdModalContent); + const LateralContentComponent = findSlotOfType(children, StdModalLateralContent); + const FooterComponent = findSlotOfType(children, StdModalFooter); + + return ( + +
+ {LateralContentComponent} +
+ {TitleComponent} + {ContentComponent} + {FooterComponent} +
+
+
+ ); +} + +const StdModal = Object.assign(StdModalComponent, { + Title: StdModalTitle, + Content: StdModalContent, + LateralContent: StdModalLateralContent, + Footer: StdModalFooter, +}); + +export default StdModal; diff --git a/src/components/common/layout/stdModal/StdModalOpener.tsx b/src/components/common/layout/stdModal/StdModalOpener.tsx new file mode 100644 index 0000000..9a43ffa --- /dev/null +++ b/src/components/common/layout/stdModal/StdModalOpener.tsx @@ -0,0 +1,22 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { ModalContext } from '@/contexts/modalContext'; +import { PropsWithChildren, useContext } from 'react'; + +type StdModalOpenerProps = PropsWithChildren<{ + modalKey: string; +}>; + +const StdModalOpener = ({ modalKey, children }: StdModalOpenerProps) => { + const modalContext = useContext(ModalContext); + + const showModal = modalContext.currentModalKey === modalKey; + if (!showModal) return null; + return children; +}; + +export default StdModalOpener; diff --git a/src/components/common/layout/stdModal/modalClassBuilder.ts b/src/components/common/layout/stdModal/modalClassBuilder.ts new file mode 100644 index 0000000..a8f75ae --- /dev/null +++ b/src/components/common/layout/stdModal/modalClassBuilder.ts @@ -0,0 +1,21 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { clsx } from 'clsx'; +import { ModalSize } from './StdModal'; + +const COMMON_MODAL_CLASSES = 'ig-scrollbar flex items-stretch overflow-hidden '; + +const SIZE_MODAL_CLASSES = { + extraSmall: 'w-70vw sm:w-45vw md:w-35vw lg:w-30vw xl:w-25vw', + small: 'w-75vw sm:w-55vw md:w-45vw lg:w-40vw xl:w-35vw', + medium: 'w-95vw sm:w-75vw md:w-65vw lg:w-60vw xl:w-55vw', + large: 'w-95vw sm:w-90vw md:w-80vw lg:w-75vw xl:w-70vw', + extraLarge: 'w-98vw xl:w-95vw', +}; + +export const modalClassBuilder = (size?: ModalSize) => + size ? clsx(COMMON_MODAL_CLASSES, SIZE_MODAL_CLASSES[size]) : COMMON_MODAL_CLASSES; diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx b/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx new file mode 100644 index 0000000..c8b47e3 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx @@ -0,0 +1,64 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import useModal from '@/hooks/useModal'; +import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; +import StdButton from '@common/base/stdButton/StdButton'; +import StdIcon from '@common/base/stdIcon/StdIcon'; +import { PropsWithChildren, ReactNode, useEffect, useRef } from 'react'; +import modalTitleClassBuilder from './modalTitleClassBuilder'; +export type StdModalTitleStatus = 'default' | 'danger'; + +type StdModalTitleProps = { + onClose?: () => void; + status?: StdModalTitleStatus; + icon?: StdIconId; + customIcon?: ReactNode; + disabledAutoFocus?: boolean; +}; + +export default function StdModalTitle({ + icon, + status = 'default', + onClose: onCloseProps, + customIcon, + disabledAutoFocus, + children, +}: PropsWithChildren) { + const buttonRef = useRef(null); + const { closeModal } = useModal(); + const onClose = onCloseProps ?? closeModal; + const { containerClasses, iconColor, childrenClasses } = modalTitleClassBuilder(status); + + useEffect(() => { + if (!disabledAutoFocus) { + buttonRef.current?.focus(); + buttonRef.current?.blur(); + } + }, [disabledAutoFocus]); + + return ( +
+ {customIcon ? ( +
{customIcon}
+ ) : ( + icon && ( +
+ +
+ ) + )} +
{children}
+ +
+ ); +} diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/modalTitleClassBuilder.ts b/src/components/common/layout/stdModal/slots/StdModalTitle/modalTitleClassBuilder.ts new file mode 100644 index 0000000..c042225 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/StdModalTitle/modalTitleClassBuilder.ts @@ -0,0 +1,30 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { clsx } from 'clsx'; +import { StdModalTitleStatus } from './StdModalTitle'; + +export const COMMON_CONTAINER_CLASSES = 'flex min-w-full max-w-fit items-center gap-1 border-b p-2 h-8'; + +export const CHILDREN_CLASSES = 'text-heading-s line-clamp-2 grow font-semibold text-left'; + +export const CONTAINER_STATUS_CLASSES = { + default: 'border-b-gray-300 bg-gray-100', + danger: 'border-b-error-600 bg-error-100', +}; + +export const ICON_STATUS_COLOR_CLASSES = { + default: 'gray-900', + danger: 'error-600', +} as const; + +export default function modalTitleClassBuilder(status: StdModalTitleStatus) { + return { + containerClasses: clsx(COMMON_CONTAINER_CLASSES, CONTAINER_STATUS_CLASSES[status]), + iconColor: ICON_STATUS_COLOR_CLASSES[status], + childrenClasses: CHILDREN_CLASSES, + }; +} diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts b/src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts new file mode 100644 index 0000000..8f4db35 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts @@ -0,0 +1,32 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import modalTitleClassBuilder, { + CHILDREN_CLASSES, + COMMON_CONTAINER_CLASSES, + CONTAINER_STATUS_CLASSES, + ICON_STATUS_COLOR_CLASSES, +} from '../modalTitleClassBuilder'; + +describe('modalTitleClassBuilder function', () => { + it('should have the common classes', () => { + const classes = modalTitleClassBuilder('default'); + expect(classes.containerClasses.includes(COMMON_CONTAINER_CLASSES)).toBe(true); + expect(classes.childrenClasses).toBe(CHILDREN_CLASSES); + }); + + it('should have the default classes', () => { + const classes = modalTitleClassBuilder('default'); + expect(classes.containerClasses.includes(CONTAINER_STATUS_CLASSES.default)).toBe(true); + expect(classes.iconColor.includes(ICON_STATUS_COLOR_CLASSES.default)).toBe(true); + }); + + it('should have the danger classes', () => { + const classes = modalTitleClassBuilder('danger'); + expect(classes.containerClasses.includes(CONTAINER_STATUS_CLASSES.danger)).toBe(true); + expect(classes.iconColor.includes(ICON_STATUS_COLOR_CLASSES.danger)).toBe(true); + }); +}); diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx b/src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx new file mode 100644 index 0000000..bd0bd93 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx @@ -0,0 +1,30 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { render, screen } from '@testing-library/react'; + +import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; +import StdModalTitle from '../StdModalTitle'; + +const onClose = vitest.fn(); +const TEST_CHILDREN =
; +const TEST_ICON = StdIconId.Warning; + +describe('StdModalTitle', () => { + it('render the default StdModalTitle', () => { + render({TEST_CHILDREN}); + expect(screen.getByRole('banner')).toBeInTheDocument(); + }); + + it('render the StdModalTitle with icon', () => { + render( + + {TEST_CHILDREN} + , + ); + expect(screen.getByTitle(TEST_ICON)).toBeInTheDocument(); + }); +}); diff --git a/src/components/common/layout/stdModal/slots/stdModalContents/StdModalContent.tsx b/src/components/common/layout/stdModal/slots/stdModalContents/StdModalContent.tsx new file mode 100644 index 0000000..2204f5e --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalContents/StdModalContent.tsx @@ -0,0 +1,11 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { PropsWithChildren } from 'react'; + +export default function StdModalContent({ children }: PropsWithChildren) { + return
{children}
; +} diff --git a/src/components/common/layout/stdModal/slots/stdModalContents/StdModalLateralContent.tsx b/src/components/common/layout/stdModal/slots/stdModalContents/StdModalLateralContent.tsx new file mode 100644 index 0000000..9e2f77f --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalContents/StdModalLateralContent.tsx @@ -0,0 +1,11 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { PropsWithChildren } from 'react'; + +export default function StdModalLateralContent({ children }: PropsWithChildren) { + return
{children}
; +} diff --git a/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx b/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx new file mode 100644 index 0000000..8d54b3f --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx @@ -0,0 +1,18 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { render, screen } from '@testing-library/react'; +import StdModalContent from '../StdModalContent'; + +const CHILD_TEST =
; + +describe('StdModalContent', () => { + it('render the components and its children', () => { + render({CHILD_TEST}); + const child = screen.getByRole('article'); + expect(child).toBeInTheDocument(); + }); +}); diff --git a/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx b/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx new file mode 100644 index 0000000..bd969a5 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx @@ -0,0 +1,18 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { render, screen } from '@testing-library/react'; +import StdModalLateralContent from '../StdModalLateralContent'; + +const CHILD_TEST =
; + +describe('StdModalLateralContent', () => { + it('render the components and its children', () => { + render({CHILD_TEST}); + const child = screen.getByRole('article'); + expect(child).toBeInTheDocument(); + }); +}); diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx b/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx new file mode 100644 index 0000000..4688028 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx @@ -0,0 +1,37 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; +import StdIcon from '@common/base/stdIcon/StdIcon'; + +import { PropsWithChildren } from 'react'; +import modalFooterClassBuilder from './modalFooterClassBuilder'; + +export type StdFooterInformation = { + icon: StdIconId; + text: string; +}; +type StdModalFooterProps = { + info?: StdFooterInformation; +}; + +const ICON_SIZE = 16; + +export default function StdModalFooter({ children, info }: PropsWithChildren) { + const { containerClasses, childrenClasses, infoClasses } = modalFooterClassBuilder(); + return ( +
+
{children}
+ + {info && ( + + + {info.text} + + )} +
+ ); +} diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/modalFooterClassBuilder.ts b/src/components/common/layout/stdModal/slots/stdModalFooter/modalFooterClassBuilder.ts new file mode 100644 index 0000000..771b057 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalFooter/modalFooterClassBuilder.ts @@ -0,0 +1,19 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export const CONTAINER_CLASSES = 'flex min-w-full max-w-fit flex-col gap-2 self-end px-2 pb-2'; + +export const CHILDREN_CLASSES = 'flex flex-wrap justify-end gap-2'; + +export const INFO_CLASSES = 'flex items-center justify-end gap-1 text-caption text-gray-600'; + +export default function modalFooterClassBuilder() { + return { + containerClasses: CONTAINER_CLASSES, + childrenClasses: CHILDREN_CLASSES, + infoClasses: INFO_CLASSES, + }; +} diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts b/src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts new file mode 100644 index 0000000..82a4500 --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts @@ -0,0 +1,16 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import modalFooterClassBuilder, { CHILDREN_CLASSES, CONTAINER_CLASSES, INFO_CLASSES } from '../modalFooterClassBuilder'; + +describe('modalFooterClassBuilder function', () => { + it('should have the common classes', () => { + const classes = modalFooterClassBuilder(); + expect(classes.containerClasses.includes(CONTAINER_CLASSES)).toBe(true); + expect(classes.childrenClasses.includes(CHILDREN_CLASSES)).toBe(true); + expect(classes.infoClasses.includes(INFO_CLASSES)).toBe(true); + }); +}); diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx b/src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx new file mode 100644 index 0000000..ae88c1e --- /dev/null +++ b/src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx @@ -0,0 +1,56 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import StdButton from '@/components/common/base/stdButton/StdButton'; +import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; +import { render, screen } from '@testing-library/react'; +import StdModalFooter, { StdFooterInformation } from '../StdModalFooter'; + +const TEST_BUTTON = ; +const TEST_BUTTONS_ARRAY = [ + , + , + , +]; +const TEST_TEXT_INFORMATION = 'Information'; +const TEST_ICON_INFORMATION = StdIconId.Info; +const TEST_INFORMATION: StdFooterInformation = { + icon: TEST_ICON_INFORMATION, + text: TEST_TEXT_INFORMATION, +}; + +describe('StdModalFooter', () => { + it('renders the default StdModalFooter', () => { + render({TEST_BUTTON}); + const footer = screen.getByRole('group'); + expect(footer).toBeInTheDocument(); + }); + + it('render the StdModalFooter with a defined number of buttons', () => { + render( + + {TEST_BUTTONS_ARRAY.map((btn, idx) => ( + {btn} + ))} + , + ); + const buttons = screen.getAllByRole('button'); + expect(buttons.length).toBe(TEST_BUTTONS_ARRAY.length); + }); + + it('render the StdModalFooter with only one button', () => { + render({TEST_BUTTON}); + const buttons = screen.getAllByRole('button'); + expect(buttons.length).toBe(1); + }); + + it('render the StdModalFooter with an information', () => { + render({TEST_BUTTON}); + expect(screen.getByRole('note')).toBeInTheDocument(); + expect(screen.getByTitle(TEST_ICON_INFORMATION)).toBeInTheDocument(); + expect(screen.getByText(TEST_TEXT_INFORMATION)).toBeInTheDocument(); + }); +}); diff --git a/src/components/common/layout/stdModal/stdModalWrapper/StdModalWrapper.tsx b/src/components/common/layout/stdModal/stdModalWrapper/StdModalWrapper.tsx new file mode 100644 index 0000000..529cf89 --- /dev/null +++ b/src/components/common/layout/stdModal/stdModalWrapper/StdModalWrapper.tsx @@ -0,0 +1,73 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import useFocusTrapping from '@/hooks/useFocusTrapping'; +import useModal from '@/hooks/useModal'; +import { MouseEventHandler, PropsWithChildren, useEffect, useMemo, useRef } from 'react'; +import { createPortal } from 'react-dom'; +import modalWrapperClassBuilder from './modalClassBuilder'; + +export type StdModalProps = { + onClose?: () => void; + element?: HTMLElement; +}; + +export default function StdModalWrapper({ onClose: onCloseProps, children }: PropsWithChildren) { + const { closeModal } = useModal(); + const onClose = onCloseProps ?? closeModal; + const mouseDownRef = useRef(false); + useEffect(() => { + mouseDownRef.current = false; + document.body.classList.add('overflow-hidden'); + return () => document.body.classList.remove('overflow-hidden'); + }, []); + + const { containerClasses, subContainerClasses, modalClasses } = modalWrapperClassBuilder(); + + const handlesCloseModal = useMemo( + () => ({ + onMouseDown: () => { + mouseDownRef.current = true; + }, + onMouseUp: () => { + if (mouseDownRef.current) { + onClose?.(); + return; + } + mouseDownRef.current = false; + }, + }), + [onClose], + ); + + const stopPropagation: Record> = useMemo( + () => ({ + onMouseDown: (e) => { + mouseDownRef.current = false; + e.stopPropagation(); + }, + onMouseUp: (e) => { + mouseDownRef.current = false; + e.stopPropagation(); + }, + }), + [], + ); + + const containerElementRef = useRef(null); + useFocusTrapping(containerElementRef, true); + + return createPortal( +
+
+
+ {children} +
+
+
, + document.body, + ); +} diff --git a/src/components/common/layout/stdModal/stdModalWrapper/modalClassBuilder.ts b/src/components/common/layout/stdModal/stdModalWrapper/modalClassBuilder.ts new file mode 100644 index 0000000..0e9b92d --- /dev/null +++ b/src/components/common/layout/stdModal/stdModalWrapper/modalClassBuilder.ts @@ -0,0 +1,20 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +export const CONTAINER_CLASSES = + 'fixed left-0 top-0 z-50 flex h-full w-full items-start justify-center overflow-auto bg-gray-900/25'; + +export const SUB_CONTAINER_CLASSES = 'flex min-h-full min-w-full items-center justify-center p-2'; + +export const MODAL_CLASSES = 'rounded bg-gray-w shadow-4 overflow-auto'; + +export default function modalWrapperClassBuilder() { + return { + containerClasses: CONTAINER_CLASSES, + subContainerClasses: SUB_CONTAINER_CLASSES, + modalClasses: MODAL_CLASSES, + }; +} diff --git a/src/components/common/layout/stdModal/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts b/src/components/common/layout/stdModal/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts new file mode 100644 index 0000000..5bd2f34 --- /dev/null +++ b/src/components/common/layout/stdModal/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts @@ -0,0 +1,20 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import modalWrapperClassBuilder, { + CONTAINER_CLASSES, + MODAL_CLASSES, + SUB_CONTAINER_CLASSES, +} from '../modalClassBuilder'; + +describe('modalWrapperClassBuilder function', () => { + it('should have common classes', () => { + const classes = modalWrapperClassBuilder(); + expect(classes.containerClasses.includes(CONTAINER_CLASSES)).toBe(true); + expect(classes.subContainerClasses.includes(SUB_CONTAINER_CLASSES)).toBe(true); + expect(classes.modalClasses.includes(MODAL_CLASSES)).toBe(true); + }); +}); diff --git a/src/components/common/layout/stdModal/stdModalWrapper/tests/stdModalWrapper.test.tsx b/src/components/common/layout/stdModal/stdModalWrapper/tests/stdModalWrapper.test.tsx new file mode 100644 index 0000000..8238091 --- /dev/null +++ b/src/components/common/layout/stdModal/stdModalWrapper/tests/stdModalWrapper.test.tsx @@ -0,0 +1,18 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { render, screen } from '@testing-library/react'; +import StdModalWrapper from '../StdModalWrapper'; + +const TEST_CHILDREN =
Content
; + +describe('StdModalWrapper', () => { + it('render the stdModalWrapper component', () => { + render({TEST_CHILDREN}); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('article')).toBeInTheDocument(); + }); +}); diff --git a/src/components/common/layout/stdModal/tests/stdModal.test.tsx b/src/components/common/layout/stdModal/tests/stdModal.test.tsx new file mode 100644 index 0000000..da99e4b --- /dev/null +++ b/src/components/common/layout/stdModal/tests/stdModal.test.tsx @@ -0,0 +1,58 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { render, screen } from '@testing-library/react'; + +import StdButton from '@/components/common/base/stdButton/StdButton'; +import StdModal from '../StdModal'; + +const onClose = vitest.fn(); +const TEST_ID = 'some-id'; +const TEST_TITLE =
Modal Title
; +const TEST_CHILDREN =
Main Content
; +const TEST_LATERAL_CHILDREN =
Lateral Content
; +const TEST_BUTTONS = [, ]; + +describe('StdModal', () => { + it('renders the StdModal with the proper id when specified', () => { + render( + + {TEST_CHILDREN} + , + ); + expect(document.querySelector(`#${TEST_ID}`)).toBeInTheDocument(); + }); + + it('render the StdModal without lateral content', () => { + render( + + {TEST_TITLE} + {TEST_CHILDREN} + {TEST_BUTTONS} + , + ); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('banner')).toBeInTheDocument(); + expect(screen.getByRole('article')).toBeInTheDocument(); + expect(screen.getByRole('group')).toBeInTheDocument(); + }); + + it('render the StdModal with lateral content ', () => { + render( + + {TEST_TITLE} + {TEST_CHILDREN} + {TEST_LATERAL_CHILDREN} + {TEST_BUTTONS} + , + ); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('banner')).toBeInTheDocument(); + expect(screen.getByRole('article')).toBeInTheDocument(); + expect(screen.getByRole('region')).toBeInTheDocument(); + expect(screen.getByRole('group')).toBeInTheDocument(); + }); +}); diff --git a/src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx b/src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx new file mode 100644 index 0000000..16ddf82 --- /dev/null +++ b/src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import StdModal from '@/components/common/layout/stdModal/StdModal'; +import StdModalOpener from '@/components/common/layout/stdModal/StdModalOpener'; +import { ModalContext } from '@/contexts/modalContext'; +import ModalProvider from '@/providers/modalProvider'; +import { noop } from '@/shared/utils/defaultUtils'; +import { render, screen } from '@testing-library/react'; +import { PropsWithChildren } from 'react'; + +const TEST_TEXT = 'My component wrapped in the modal'; +const TEST_COMPONENT = {TEST_TEXT}; +const TEST_MODAL_KEY = 'test-modal-key'; +const TEST_OTHER_MODAL_KEY = 'other-test-modal-key'; + +vi.mock('@/providers/modalProvider.tsx', () => ({ + default: ({ children }: PropsWithChildren) => ( + true }} + > + {children} + + ), +})); + +describe('useModal', () => { + it('render the expected component in the modal when the correct modal key is setted', () => { + render( + + + {TEST_COMPONENT} + + , + ); + expect(screen.getByRole('article')).toBeInTheDocument(); + }); + it('do not render the component in a modal when the wrong modal key is setted', () => { + render( + + + {TEST_COMPONENT} + + , + ); + expect(screen.queryByRole('article')).not.toBeInTheDocument(); + }); +}); diff --git a/src/shared/utils/slotsUtils.ts b/src/shared/utils/slotsUtils.ts new file mode 100644 index 0000000..e439d48 --- /dev/null +++ b/src/shared/utils/slotsUtils.ts @@ -0,0 +1,15 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import React, { Children, FunctionComponent, ReactElement } from 'react'; + +export const findSlotOfType = ( + children: ReactElement | ReactElement[] | undefined, + slotType: FunctionComponent, +): ReactElement | null => + Children.toArray(children).find( + (child) => React.isValidElement(child) && child.type === slotType, + ) as ReactElement | null; From 176857462113557dfee6823be0594be4a02c615a Mon Sep 17 00:00:00 2001 From: vargastat Date: Wed, 18 Dec 2024 16:27:51 +0100 Subject: [PATCH 2/4] fix: work ongoing --- src/components/common/layout/stdModal/StdModal.tsx | 2 +- .../layout/{stdModal => }/stdModalWrapper/StdModalWrapper.tsx | 0 .../layout/{stdModal => }/stdModalWrapper/modalClassBuilder.ts | 0 .../stdModalWrapper/tests/modalWrapperClassBuilder.test.ts | 0 .../stdModalWrapper/tests/stdModalWrapper.test.tsx | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename src/components/common/layout/{stdModal => }/stdModalWrapper/StdModalWrapper.tsx (100%) rename src/components/common/layout/{stdModal => }/stdModalWrapper/modalClassBuilder.ts (100%) rename src/components/common/layout/{stdModal => }/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts (100%) rename src/components/common/layout/{stdModal => }/stdModalWrapper/tests/stdModalWrapper.test.tsx (100%) diff --git a/src/components/common/layout/stdModal/StdModal.tsx b/src/components/common/layout/stdModal/StdModal.tsx index 3ae20ff..9094b24 100644 --- a/src/components/common/layout/stdModal/StdModal.tsx +++ b/src/components/common/layout/stdModal/StdModal.tsx @@ -12,7 +12,7 @@ import StdModalTitle from './slots/StdModalTitle/StdModalTitle'; import StdModalContent from './slots/stdModalContents/StdModalContent'; import StdModalLateralContent from './slots/stdModalContents/StdModalLateralContent'; import StdModalFooter from './slots/stdModalFooter/StdModalFooter'; -import StdModalWrapper, { StdModalProps } from './stdModalWrapper/StdModalWrapper'; +import StdModalWrapper, { StdModalProps } from '../stdModalWrapper/StdModalWrapper'; export type ModalSize = 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge'; diff --git a/src/components/common/layout/stdModal/stdModalWrapper/StdModalWrapper.tsx b/src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx similarity index 100% rename from src/components/common/layout/stdModal/stdModalWrapper/StdModalWrapper.tsx rename to src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx diff --git a/src/components/common/layout/stdModal/stdModalWrapper/modalClassBuilder.ts b/src/components/common/layout/stdModalWrapper/modalClassBuilder.ts similarity index 100% rename from src/components/common/layout/stdModal/stdModalWrapper/modalClassBuilder.ts rename to src/components/common/layout/stdModalWrapper/modalClassBuilder.ts diff --git a/src/components/common/layout/stdModal/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts b/src/components/common/layout/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts similarity index 100% rename from src/components/common/layout/stdModal/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts rename to src/components/common/layout/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts diff --git a/src/components/common/layout/stdModal/stdModalWrapper/tests/stdModalWrapper.test.tsx b/src/components/common/layout/stdModalWrapper/tests/stdModalWrapper.test.tsx similarity index 100% rename from src/components/common/layout/stdModal/stdModalWrapper/tests/stdModalWrapper.test.tsx rename to src/components/common/layout/stdModalWrapper/tests/stdModalWrapper.test.tsx From d1080798e652cad149dd162bcd65236ea43eee6b Mon Sep 17 00:00:00 2001 From: vargastat Date: Wed, 18 Dec 2024 18:05:35 +0100 Subject: [PATCH 3/4] fix: work ongoing --- .../slots/StdModalTitle/StdModalTitle.tsx | 2 +- .../stdModalWrapper/StdModalWrapper.tsx | 2 +- src/hooks/useFocusTrapping.ts | 55 +++++++++++++++++++ .../home/components/StudyTableDisplay.tsx | 22 ++++++-- .../home/components/StudyTableUtils.tsx | 16 ++++++ src/shared/i18n/en.json | 3 +- 6 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 src/hooks/useFocusTrapping.ts diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx b/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx index c8b47e3..15dee68 100644 --- a/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx +++ b/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx @@ -5,11 +5,11 @@ */ import useModal from '@/hooks/useModal'; -import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; import StdButton from '@common/base/stdButton/StdButton'; import StdIcon from '@common/base/stdIcon/StdIcon'; import { PropsWithChildren, ReactNode, useEffect, useRef } from 'react'; import modalTitleClassBuilder from './modalTitleClassBuilder'; +import { StdIconId } from '@/shared/utils/common/mappings/iconMaps'; export type StdModalTitleStatus = 'default' | 'danger'; type StdModalTitleProps = { diff --git a/src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx b/src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx index 529cf89..5cb219b 100644 --- a/src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx +++ b/src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx @@ -4,11 +4,11 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import useFocusTrapping from '@/hooks/useFocusTrapping'; import useModal from '@/hooks/useModal'; import { MouseEventHandler, PropsWithChildren, useEffect, useMemo, useRef } from 'react'; import { createPortal } from 'react-dom'; import modalWrapperClassBuilder from './modalClassBuilder'; +import useFocusTrapping from '@/hooks/useFocusTrapping'; export type StdModalProps = { onClose?: () => void; diff --git a/src/hooks/useFocusTrapping.ts b/src/hooks/useFocusTrapping.ts new file mode 100644 index 0000000..4f9df88 --- /dev/null +++ b/src/hooks/useFocusTrapping.ts @@ -0,0 +1,55 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { useEffect } from 'react'; + +const FOCUSABLE_ELEMENTS = [ + 'button', + 'a[href]', + 'input', + 'select', + 'textarea', + 'details', + '[tabindex]:not([tabindex="-1"])', +]; +const FOCUSABLE_ELEMENTS_QUERY = FOCUSABLE_ELEMENTS.map((elmt) => elmt + ':not([disabled]):not([aria-hidden])').join( + ',', +); + +const getFocusableElements = (containerElement: HTMLElement) => { + const elmts = containerElement.querySelectorAll(FOCUSABLE_ELEMENTS_QUERY) as unknown as HTMLElement[]; + return [elmts[0], elmts[elmts.length - 1]]; +}; + +const useFocusTrapping = (ref: React.RefObject, show: boolean) => { + useEffect(() => { + if (!show || !ref.current) { + return; + } + const containerElement = ref.current; + + const handleTabKeyPress = (event: KeyboardEvent) => { + const [firstElement, lastElement] = getFocusableElements(containerElement); + if (event.key === 'Tab') { + if (event.shiftKey && document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus(); + } else if (!event.shiftKey && document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus(); + } + } + }; + + containerElement.addEventListener('keydown', handleTabKeyPress); + + return () => { + containerElement.removeEventListener('keydown', handleTabKeyPress); + }; + }, [ref, show]); +}; + +export default useFocusTrapping; diff --git a/src/pages/pegase/home/components/StudyTableDisplay.tsx b/src/pages/pegase/home/components/StudyTableDisplay.tsx index 29c1419..f56c923 100644 --- a/src/pages/pegase/home/components/StudyTableDisplay.tsx +++ b/src/pages/pegase/home/components/StudyTableDisplay.tsx @@ -7,12 +7,14 @@ import StdSimpleTable from '@/components/common/data/stdSimpleTable/StdSimpleTab import { useState } from 'react'; import { StudyDTO } from '@/shared/types/index'; import getStudyTableHeaders from './StudyTableHeaders'; -import { addSortColumn } from './StudyTableUtils'; +import { addSortColumn, useNewStudyModal } from './StudyTableUtils'; import StudiesPagination from './StudiesPagination'; import { RowSelectionState } from '@tanstack/react-table'; import StdButton from '@/components/common/base/stdButton/StdButton'; import { StudyStatus } from '@/shared/types/common/StudyStatus.type'; import { useStudyTableDisplay } from './useStudyTableDisplay'; +import StdModal from '@/components/common/layout/stdModal/StdModal'; +import { useTranslation } from 'react-i18next'; interface StudyTableDisplayProps { searchStudy: string | undefined; @@ -24,6 +26,8 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = const [rowSelection, setRowSelection] = useState({}); const [sortedColumn, setSortedColumn] = useState('status'); const [isHeaderHovered, setIsHeaderHovered] = useState(false); + const { isModalOpen, toggleModal } = useNewStudyModal(); + const { t } = useTranslation(); const handleSort = (column: string) => { const newSortOrder = sortByState[column] === 'asc' ? 'desc' : 'asc'; @@ -35,7 +39,6 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = }; const headers = getStudyTableHeaders(); - console.log('Original Headers:', headers); const sortedHeaders = addSortColumn( headers, @@ -46,8 +49,6 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = isHeaderHovered, ); - console.log('Sorted Headers:', sortedHeaders); - const { rows, count, intervalSize, current, setPage } = useStudyTableDisplay({ searchStudy, projectId, @@ -101,7 +102,18 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = /> ) : ( - console.log('NewStudy')} /> + + )} + {isModalOpen && ( + + {t('home.@new_study')} + +

Here you can create a new study. Add your content here.

+
+ + + +
)} diff --git a/src/pages/pegase/home/components/StudyTableUtils.tsx b/src/pages/pegase/home/components/StudyTableUtils.tsx index 928f6d9..51f2208 100644 --- a/src/pages/pegase/home/components/StudyTableUtils.tsx +++ b/src/pages/pegase/home/components/StudyTableUtils.tsx @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* * 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 @@ -6,6 +8,7 @@ import StdIcon from '@/components/common/base/stdIcon/StdIcon'; import { StdIconId } from '@/shared/utils/common/mappings/iconMaps'; +import { useState } from 'react'; export function addSortColumn( headers: any[], @@ -60,3 +63,16 @@ export function addSortColumn( }; }); } + +export function useNewStudyModal() { + const [isModalOpen, setModalOpen] = useState(false); + + const toggleModal = () => { + setModalOpen((prev) => !prev); + }; + + return { + isModalOpen, + toggleModal, + }; +} diff --git a/src/shared/i18n/en.json b/src/shared/i18n/en.json index 86da2b6..bbfa688 100644 --- a/src/shared/i18n/en.json +++ b/src/shared/i18n/en.json @@ -37,7 +37,8 @@ "@keywords": "Keywords", "@project": "Project", "@my_studies": "My studies", - "@my_projects": "My projects" + "@my_projects": "My projects", + "@new_study": "New Study" }, "project": { "@setting": "Setting", From a563753b3b169c56a5062d37e5477c464f6705f9 Mon Sep 17 00:00:00 2001 From: MOUAD EL AZAAR Date: Tue, 31 Dec 2024 18:21:42 +0100 Subject: [PATCH 4/4] feat: create and duplicate study --- package-lock.json | 430 +++++++++++++++--- package.json | 8 +- src/App.css | 1 + .../forms/stdInputText/StdInputText.tsx | 109 +++++ .../stdInputText/tests/stdInputText.test.tsx | 93 ++++ .../tests/textClassBuilder.test.ts | 66 +++ .../forms/stdInputText/textClassBuilder.ts | 42 ++ .../slots/StdModalTitle/StdModalTitle.tsx | 8 +- .../tests/modalTitleClassBuilder.test.ts | 32 -- .../tests/stdModalTitle.test.tsx | 30 -- .../tests/stdModalContent.test.tsx | 18 - .../tests/stdModalLateralContent.test.tsx | 18 - .../slots/stdModalFooter/StdModalFooter.tsx | 2 +- .../tests/modalFooterClassBuilder.test.ts | 16 - .../tests/stdModalFooter.test.tsx | 56 --- .../layout/stdModal/tests/stdModal.test.tsx | 58 --- .../stdModal/tests/stdModalOpener.test.tsx | 51 --- src/index.css | 3 - src/main.tsx | 13 +- .../home/components/StudyTableDisplay.tsx | 32 +- .../pegase/home/components/studyService.ts | 51 +++ .../home/components/useStudyTableDisplay.ts | 2 +- src/pages/pegase/studies/HorizonInput.tsx | 106 +++++ src/pages/pegase/studies/KeywordsInput.tsx | 136 ++++++ src/pages/pegase/studies/ProjectInput.tsx | 92 ++++ .../pegase/studies/StudyCreationModal.tsx | 121 +++++ src/shared/i18n/en.json | 3 +- src/shared/types/pegase/study.ts | 1 + tsconfig.tsbuildinfo | 2 +- vite.config.js | 13 +- vite.config.ts | 12 +- 31 files changed, 1233 insertions(+), 392 deletions(-) create mode 100644 src/components/common/forms/stdInputText/StdInputText.tsx create mode 100644 src/components/common/forms/stdInputText/tests/stdInputText.test.tsx create mode 100644 src/components/common/forms/stdInputText/tests/textClassBuilder.test.ts create mode 100644 src/components/common/forms/stdInputText/textClassBuilder.ts delete mode 100644 src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts delete mode 100644 src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx delete mode 100644 src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx delete mode 100644 src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx delete mode 100644 src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts delete mode 100644 src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx delete mode 100644 src/components/common/layout/stdModal/tests/stdModal.test.tsx delete mode 100644 src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx create mode 100644 src/pages/pegase/home/components/studyService.ts create mode 100644 src/pages/pegase/studies/HorizonInput.tsx create mode 100644 src/pages/pegase/studies/KeywordsInput.tsx create mode 100644 src/pages/pegase/studies/ProjectInput.tsx create mode 100644 src/pages/pegase/studies/StudyCreationModal.tsx diff --git a/package-lock.json b/package-lock.json index 2f2dcc4..6e2bff8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,12 @@ "postcss": "^8.4.39", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-i18next": "^14.1.3", - "react-router-dom": "^6.24.1", + "react-i18next": "^15.0.3", + "react-router-dom": "^7.0.2", "react-toastify": "^10.0.5", - "uuid": "^11.0.3" + "rte-design-system-react": "^0.1.0", + "uuid": "^11.0.3", + "vite-plugin-top-level-await": "^1.4.4" }, "devDependencies": { "@testing-library/jest-dom": "^6.4.6", @@ -474,7 +476,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -491,7 +492,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -508,7 +508,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -525,7 +524,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -542,7 +540,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -559,7 +556,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -576,7 +572,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -593,7 +588,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -610,7 +604,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -627,7 +620,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -644,7 +636,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -661,7 +652,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -678,7 +668,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -695,7 +684,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -712,7 +700,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -729,7 +716,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,7 +732,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -763,7 +748,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -780,7 +764,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -797,7 +780,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -814,7 +796,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -831,7 +812,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -848,7 +828,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1365,13 +1344,21 @@ "node": ">=14" } }, - "node_modules/@remix-run/router": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", - "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", "license": "MIT", "engines": { "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -1381,7 +1368,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1395,7 +1381,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1409,7 +1394,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1423,7 +1407,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1437,7 +1420,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1451,7 +1433,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1465,7 +1446,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1479,7 +1459,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1493,7 +1472,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1507,7 +1485,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1521,7 +1498,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1535,7 +1511,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1549,7 +1524,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1563,7 +1537,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1577,7 +1550,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1591,7 +1563,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1605,7 +1576,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1619,7 +1589,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1633,6 +1602,245 @@ "dev": true, "license": "MIT" }, + "node_modules/@swc/core": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core/-/core-1.10.1.tgz", + "integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.17" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.10.1", + "@swc/core-darwin-x64": "1.10.1", + "@swc/core-linux-arm-gnueabihf": "1.10.1", + "@swc/core-linux-arm64-gnu": "1.10.1", + "@swc/core-linux-arm64-musl": "1.10.1", + "@swc/core-linux-x64-gnu": "1.10.1", + "@swc/core-linux-x64-musl": "1.10.1", + "@swc/core-win32-arm64-msvc": "1.10.1", + "@swc/core-win32-ia32-msvc": "1.10.1", + "@swc/core-win32-x64-msvc": "1.10.1" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz", + "integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz", + "integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz", + "integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz", + "integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz", + "integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz", + "integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz", + "integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz", + "integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz", + "integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.10.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz", + "integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.17", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.62.8", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@tanstack/query-core/-/query-core-5.62.8.tgz", + "integrity": "sha512-4fV31vDsUyvNGrKIOUNPrZztoyL187bThnoQOvAXEVlZbSiuPONpfx53634MKKdvsDir5NyOGm80ShFaoHS/mw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.62.8", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@tanstack/react-query/-/react-query-5.62.8.tgz", + "integrity": "sha512-8TUstKxF/fysHonZsWg/hnlDVgasTdHx6Q+f1/s/oPKJBJbKUWPZEHwLTMOZgrZuroLMiqYKJ9w69Abm8mWP0Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.62.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-table": { "version": "8.20.5", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", @@ -1821,11 +2029,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -1839,7 +2052,7 @@ "version": "20.15.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.15.0.tgz", "integrity": "sha512-eQf4OkH6gA9v1W0iEpht/neozCsZKMTK+C4cU6/fv7wtJCCL8LEQ4hie2Ln8ZP/0YYM2xGj7//f8xyqItkJ6QA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.13.0" @@ -3209,6 +3422,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3845,7 +4067,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4877,7 +5098,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7819,12 +8039,12 @@ } }, "node_modules/react-i18next": { - "version": "14.1.3", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.3.tgz", - "integrity": "sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw==", + "version": "15.2.0", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/react-i18next/-/react-i18next-15.2.0.tgz", + "integrity": "sha512-iJNc8111EaDtVTVMKigvBtPHyrJV+KblWG73cUxqp+WmJCcwkzhWNFXmkAD5pwP2Z4woeDj/oXDdbjDsb3Gutg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", + "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { @@ -7858,35 +8078,43 @@ } }, "node_modules/react-router": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", - "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "version": "7.0.2", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/react-router/-/react-router-7.0.2.tgz", + "integrity": "sha512-m5AcPfTRUcjwmhBzOJGEl6Y7+Crqyju0+TgTQxoS4SO+BkWbhOrcfZNq6wSWdl2BBbJbsAoBUb8ZacOFT+/JlA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.21.0" + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8" + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-router-dom": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", - "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", + "version": "7.0.2", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/react-router-dom/-/react-router-dom-7.0.2.tgz", + "integrity": "sha512-VJOQ+CDWFDGaWdrG12Nl+d7yHtLaurNgAQZVgaIy7/Xd+DojgmYLosFfZdGz1wpxmjJIAkAMVTKWcvkx1oggAw==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.21.0", - "react-router": "6.28.0" + "react-router": "7.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": ">=18", + "react-dom": ">=18" } }, "node_modules/react-toastify": { @@ -8094,7 +8322,6 @@ "version": "4.27.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -8135,6 +8362,25 @@ "dev": true, "license": "MIT" }, + "node_modules/rte-design-system-react": { + "version": "0.1.0", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/rte-design-system-react/-/rte-design-system-react-0.1.0.tgz", + "integrity": "sha512-nSw0aM0J65iQbGeP/ehKtgIxNf9AImg3wQ6LXSaijej5bCHiBEYtDdQcbrEr2Y9ZAhNnqirO54x85NcTClBUGg==", + "license": "MPL-2.0", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "@tanstack/react-query": "^5.60.5", + "@tanstack/react-table": "^8.20.5" + }, + "peerDependencies": { + "clsx": "^2.1.1", + "i18next": "^23.16.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-i18next": "^15.0.3", + "react-router-dom": "^7.0.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8269,6 +8515,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -9029,6 +9281,12 @@ "node": "*" } }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -9174,7 +9432,7 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/universalify": { @@ -9288,7 +9546,6 @@ "version": "5.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -9367,6 +9624,33 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.4.4", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.4.tgz", + "integrity": "sha512-QyxQbvcMkgt+kDb12m2P8Ed35Sp6nXP+l8ptGrnHV9zgYDUpraO0CPdlqLSeBqvY2DToR52nutDG7mIHuysdiw==", + "license": "MIT", + "dependencies": { + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.7.0", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-top-level-await/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://devin-depot.rte-france.com/repository/npm-all/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vitest": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.6.tgz", diff --git a/package.json b/package.json index 495c833..861579c 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,12 @@ "postcss": "^8.4.39", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-i18next": "^14.1.3", - "react-router-dom": "^6.24.1", + "react-i18next": "^15.0.3", + "react-router-dom": "^7.0.2", "react-toastify": "^10.0.5", - "uuid": "^11.0.3" + "rte-design-system-react": "^0.1.0", + "uuid": "^11.0.3", + "vite-plugin-top-level-await": "^1.4.4" }, "devDependencies": { "@testing-library/jest-dom": "^6.4.6", diff --git a/src/App.css b/src/App.css index 7a1444d..6edf708 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,4 @@ +@layer rte-design-system-react @tailwind base; @tailwind components; @tailwind utilities; diff --git a/src/components/common/forms/stdInputText/StdInputText.tsx b/src/components/common/forms/stdInputText/StdInputText.tsx new file mode 100644 index 0000000..64a6a6f --- /dev/null +++ b/src/components/common/forms/stdInputText/StdInputText.tsx @@ -0,0 +1,109 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { useStdId } from '@/hooks/useStdId'; + +import StdButton from '@common/base/stdButton/StdButton'; +import { useRef } from 'react'; +import StdRequiredIndicator from '../stdRequiredIndicator/StdRequiredIndicator'; +import { textClassBuilder } from './textClassBuilder'; +import { StdChangeHandler } from '@/shared/types'; +import { StdIconId } from '@/shared/utils/common/mappings/iconMaps'; + +export type TextVariant = 'outlined' | 'text'; + +export interface StdInputTextProps { + value: string; + label?: string; + onChange?: StdChangeHandler; + onBlur?: (e: React.FocusEvent<{ value: string }>) => void; + id?: string; + placeHolder?: string; + disabled?: boolean; + variant?: TextVariant; + helperText?: string; + error?: boolean; + maxLength?: number; + password?: boolean; + required?: boolean; + autoFocus?: boolean; +} + +const StdInputText = ({ + label = '', + onChange, + onBlur, + value, + id: propsId, + variant = 'text', + disabled = false, + error = false, + password = false, + required = false, + placeHolder, + helperText, + maxLength, + autoFocus = false, +}: StdInputTextProps) => { + const inputRef = useRef(null); + const id = useStdId('input', propsId); + const { wrapperInputClasses, labelClasses, inputClasses, helperClasses, buttonClasses } = textClassBuilder( + variant, + disabled, + error, + !value || disabled, + ); + + const clearValue = () => { + if (!onChange) { + return; + } + void onChange(''); + inputRef.current?.focus(); + }; + + const handleChange = (e: React.ChangeEvent) => { + if (!onChange) { + return; + } + void onChange(e.target.value); + }; + + return ( +
+
+ + {maxLength && {`${value?.length ?? 0}/${maxLength}`}} +
+
+ +
+ +
+
+ {helperText} +
+ ); +}; + +export default StdInputText; diff --git a/src/components/common/forms/stdInputText/tests/stdInputText.test.tsx b/src/components/common/forms/stdInputText/tests/stdInputText.test.tsx new file mode 100644 index 0000000..408f081 --- /dev/null +++ b/src/components/common/forms/stdInputText/tests/stdInputText.test.tsx @@ -0,0 +1,93 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { render, screen } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; + +import { useState } from 'react'; +import StdInputText, { TextVariant } from '../StdInputText'; + +const TEST_LABEL = 'Label'; +const TEST_HELPER = 'Helper'; +const TEST_ID = 'Id'; +const TEST_PLACEHOLDER = 'Placeholder'; +const TEST_DEFAULT = 'default'; +const TEST_VARIANT = 'test' as TextVariant; + +const defaultInputSetup = () => { + const onChange = vitest.fn(); + const component = render( + , + ); + const input: HTMLInputElement = screen.getByLabelText(TEST_LABEL); + const label: HTMLSpanElement = screen.getByText(TEST_LABEL); + const helper: HTMLSpanElement = screen.getByText(TEST_HELPER); + return { ...component, input, label, helper, onChange }; +}; + +describe('StdInputText', () => { + it('renders the default StdInputText component', () => { + const { input, label, helper } = defaultInputSetup(); + expect(input).toBeInTheDocument(); + expect(label).toBeInTheDocument(); + expect(helper).toBeInTheDocument(); + expect(document.querySelector(`#${TEST_ID}`)).toBeInTheDocument(); + }); + + it('update Props should update display', () => { + const { rerender, input, label, helper } = defaultInputSetup(); + const NEW_LABEL = 'TEST_LABEL'; + const NEW_HELPER = 'TEST_HELPER'; + const NEW_PLACEHOLDER = 'TEST_PLACEHOLDER'; + const NEW_DEFAULT = 'NEW_DEFAULT'; + rerender( + , + ); + expect(input.value).toBe(NEW_DEFAULT); + expect(label.textContent).toBe(NEW_LABEL); + expect(helper.textContent).toBe(NEW_HELPER); + expect(input.placeholder).toBe(NEW_PLACEHOLDER); + }); + + it('required should add "*" to the end of label', () => { + const component = render(); + const input: HTMLInputElement = screen.getByLabelText(TEST_LABEL); + expect(input).toHaveAttribute('required'); + const star = screen.getByText('*'); + expect(star).toBeInTheDocument(); + component.rerender(); + expect(star).not.toBeInTheDocument(); + }); + + const StatefulInputTextPassword = () => { + const [value, setValue] = useState(''); + return setValue(e)} />; + }; + it('password input should not display keys', async () => { + const user = userEvent.setup(); + render(); + const input: HTMLInputElement = screen.getByLabelText(TEST_LABEL); + expect(input.value).toBe(''); + + await user.type(input, 'test'); + expect(input.value).toBe('test'); + expect(input).toHaveAttribute('type', 'password'); + }); +}); diff --git a/src/components/common/forms/stdInputText/tests/textClassBuilder.test.ts b/src/components/common/forms/stdInputText/tests/textClassBuilder.test.ts new file mode 100644 index 0000000..1f3bd00 --- /dev/null +++ b/src/components/common/forms/stdInputText/tests/textClassBuilder.test.ts @@ -0,0 +1,66 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { + textClassBuilder, + VARIANT_CLASSES, + CLEAR_CLASSES, + HELPER_CLASSES, + COMMON_VARIANT_CLASSES, + TEXT_CLASSES, + VARIANT_DISABLED_CLASSES, + ERROR_CLASSES, + INPUT_CLASSES, + BUTTON_CLASSES, + HIDE_BUTTON_CLASSES, +} from '../textClassBuilder'; + +describe('textClassBuilder function', () => { + it('should have the common classes', () => { + expect(textClassBuilder('outlined', false, false, false).wrapperInputClasses.includes(COMMON_VARIANT_CLASSES)).toBe( + true, + ); + expect(textClassBuilder('outlined', false, false, false).clearClasses.includes(CLEAR_CLASSES)).toBe(true); + expect(textClassBuilder('outlined', false, false, false).helperClasses.includes(HELPER_CLASSES)).toBe(true); + expect(textClassBuilder('outlined', false, false, false).inputClasses.includes(INPUT_CLASSES)).toBe(true); + expect(textClassBuilder('outlined', false, false, false).labelClasses.includes(TEXT_CLASSES)).toBe(true); + expect(textClassBuilder('outlined', false, false, false).buttonClasses.includes(BUTTON_CLASSES)).toBe(true); + expect(textClassBuilder('text', false, false, false).wrapperInputClasses.includes(COMMON_VARIANT_CLASSES)).toBe( + true, + ); + expect(textClassBuilder('text', false, false, false).clearClasses.includes(CLEAR_CLASSES)).toBe(true); + expect(textClassBuilder('text', false, false, false).helperClasses.includes(HELPER_CLASSES)).toBe(true); + expect(textClassBuilder('text', false, false, false).inputClasses.includes(INPUT_CLASSES)).toBe(true); + expect(textClassBuilder('text', false, false, false).labelClasses.includes(TEXT_CLASSES)).toBe(true); + expect(textClassBuilder('text', false, false, false).buttonClasses.includes(BUTTON_CLASSES)).toBe(true); + }); + it('should have the proper variant and type classes', () => { + expect( + textClassBuilder('outlined', false, false, false).wrapperInputClasses.includes(VARIANT_CLASSES.outlined), + ).toBe(true); + expect(textClassBuilder('text', false, false, false).wrapperInputClasses.includes(VARIANT_CLASSES.text)).toBe(true); + }); + it('should have the proper disabled classes', () => { + expect(textClassBuilder('outlined', true, false, false).inputClasses.includes(VARIANT_DISABLED_CLASSES)).toBe(true); + expect(textClassBuilder('outlined', true, false, false).clearClasses.includes(VARIANT_DISABLED_CLASSES)).toBe(true); + expect(textClassBuilder('text', true, false, false).inputClasses.includes(VARIANT_DISABLED_CLASSES)).toBe(true); + expect(textClassBuilder('text', true, false, false).clearClasses.includes(VARIANT_DISABLED_CLASSES)).toBe(true); + }); + it('should have the proper error classes', () => { + expect(textClassBuilder('outlined', true, true, false).inputClasses.includes(ERROR_CLASSES.input.outlined)).toBe( + true, + ); + expect(textClassBuilder('outlined', true, true, false).labelClasses.includes(ERROR_CLASSES.text)).toBe(true); + expect(textClassBuilder('outlined', true, true, false).helperClasses.includes(ERROR_CLASSES.text)).toBe(true); + expect(textClassBuilder('text', true, true, false).inputClasses.includes(ERROR_CLASSES.input.text)).toBe(true); + expect(textClassBuilder('text', true, true, false).labelClasses.includes(ERROR_CLASSES.text)).toBe(true); + expect(textClassBuilder('text', true, true, false).helperClasses.includes(ERROR_CLASSES.text)).toBe(true); + }); + it('button should be hidden when you request it', () => { + expect(textClassBuilder('outlined', false, false, true).buttonClasses.includes(HIDE_BUTTON_CLASSES)).toBe(true); + expect(textClassBuilder('text', false, false, true).buttonClasses.includes(HIDE_BUTTON_CLASSES)).toBe(true); + }); +}); diff --git a/src/components/common/forms/stdInputText/textClassBuilder.ts b/src/components/common/forms/stdInputText/textClassBuilder.ts new file mode 100644 index 0000000..c1b3845 --- /dev/null +++ b/src/components/common/forms/stdInputText/textClassBuilder.ts @@ -0,0 +1,42 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { clsx } from 'clsx'; +import type { TextVariant } from './StdInputText'; + +export const TEXT_CLASSES = 'px-0.5 py-0.25 text-body-s text-gray-700 inline-flex justify-between w-full'; +export const HELPER_CLASSES = 'h-2 px-1 text-body-s text-gray-700'; +export const CLEAR_CLASSES = 'outline-none border border-gray-w focus:rounded focus:border-primary-600'; +export const INPUT_CLASSES = + 'w-full outline-none bg-transparent [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none'; + +export const COMMON_VARIANT_CLASSES = 'h-4 pl-2 pr-2 w-full outline-none border inline-flex'; + +export const VARIANT_CLASSES = { + outlined: 'bg-gray-w hover:bg-gray-50 focus-within:border-primary-600 rounded border-gray-400', + text: 'bg-gray-w hover:bg-gray-50 focus-within:border-b-primary-600 border-gray-w border-b-gray-400', +}; +export const VARIANT_DISABLED_CLASSES = '[&]:bg-gray-100 hover:bg-gray-100 [&]:text-gray-500 [&]:cursor-not-allowed'; + +export const BUTTON_CLASSES = 'flex w-2.5 [&>button]:p-0'; +export const HIDE_BUTTON_CLASSES = 'invisible'; + +export const ERROR_CLASSES = { + text: '[&&]:text-error-600', + input: { + outlined: '[&]:border-error-600', + text: '[&]:border-b-error-600', + }, +}; + +export const textClassBuilder = (variant: TextVariant, disabled: boolean, error: boolean, hideButton: boolean) => ({ + labelClasses: clsx(TEXT_CLASSES, error && ERROR_CLASSES.text), + wrapperInputClasses: clsx(COMMON_VARIANT_CLASSES, VARIANT_CLASSES[variant]), + inputClasses: clsx(INPUT_CLASSES, disabled && VARIANT_DISABLED_CLASSES, error && ERROR_CLASSES.input[variant]), + helperClasses: clsx(HELPER_CLASSES, error && ERROR_CLASSES.text), + clearClasses: clsx(CLEAR_CLASSES, disabled && VARIANT_DISABLED_CLASSES), + buttonClasses: clsx(BUTTON_CLASSES, hideButton && HIDE_BUTTON_CLASSES), +}); diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx b/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx index 15dee68..647c395 100644 --- a/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx +++ b/src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx @@ -52,13 +52,7 @@ export default function StdModalTitle({ ) )}
{children}
- + ); } diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts b/src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts deleted file mode 100644 index 8f4db35..0000000 --- a/src/components/common/layout/stdModal/slots/StdModalTitle/tests/modalTitleClassBuilder.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import modalTitleClassBuilder, { - CHILDREN_CLASSES, - COMMON_CONTAINER_CLASSES, - CONTAINER_STATUS_CLASSES, - ICON_STATUS_COLOR_CLASSES, -} from '../modalTitleClassBuilder'; - -describe('modalTitleClassBuilder function', () => { - it('should have the common classes', () => { - const classes = modalTitleClassBuilder('default'); - expect(classes.containerClasses.includes(COMMON_CONTAINER_CLASSES)).toBe(true); - expect(classes.childrenClasses).toBe(CHILDREN_CLASSES); - }); - - it('should have the default classes', () => { - const classes = modalTitleClassBuilder('default'); - expect(classes.containerClasses.includes(CONTAINER_STATUS_CLASSES.default)).toBe(true); - expect(classes.iconColor.includes(ICON_STATUS_COLOR_CLASSES.default)).toBe(true); - }); - - it('should have the danger classes', () => { - const classes = modalTitleClassBuilder('danger'); - expect(classes.containerClasses.includes(CONTAINER_STATUS_CLASSES.danger)).toBe(true); - expect(classes.iconColor.includes(ICON_STATUS_COLOR_CLASSES.danger)).toBe(true); - }); -}); diff --git a/src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx b/src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx deleted file mode 100644 index bd0bd93..0000000 --- a/src/components/common/layout/stdModal/slots/StdModalTitle/tests/stdModalTitle.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import { render, screen } from '@testing-library/react'; - -import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; -import StdModalTitle from '../StdModalTitle'; - -const onClose = vitest.fn(); -const TEST_CHILDREN =
; -const TEST_ICON = StdIconId.Warning; - -describe('StdModalTitle', () => { - it('render the default StdModalTitle', () => { - render({TEST_CHILDREN}); - expect(screen.getByRole('banner')).toBeInTheDocument(); - }); - - it('render the StdModalTitle with icon', () => { - render( - - {TEST_CHILDREN} - , - ); - expect(screen.getByTitle(TEST_ICON)).toBeInTheDocument(); - }); -}); diff --git a/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx b/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx deleted file mode 100644 index 8d54b3f..0000000 --- a/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalContent.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import { render, screen } from '@testing-library/react'; -import StdModalContent from '../StdModalContent'; - -const CHILD_TEST =
; - -describe('StdModalContent', () => { - it('render the components and its children', () => { - render({CHILD_TEST}); - const child = screen.getByRole('article'); - expect(child).toBeInTheDocument(); - }); -}); diff --git a/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx b/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx deleted file mode 100644 index bd969a5..0000000 --- a/src/components/common/layout/stdModal/slots/stdModalContents/tests/stdModalLateralContent.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import { render, screen } from '@testing-library/react'; -import StdModalLateralContent from '../StdModalLateralContent'; - -const CHILD_TEST =
; - -describe('StdModalLateralContent', () => { - it('render the components and its children', () => { - render({CHILD_TEST}); - const child = screen.getByRole('article'); - expect(child).toBeInTheDocument(); - }); -}); diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx b/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx index 4688028..7915f61 100644 --- a/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx +++ b/src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx @@ -4,7 +4,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; +import { StdIconId } from '@/shared/utils/common/mappings/iconMaps'; import StdIcon from '@common/base/stdIcon/StdIcon'; import { PropsWithChildren } from 'react'; diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts b/src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts deleted file mode 100644 index 82a4500..0000000 --- a/src/components/common/layout/stdModal/slots/stdModalFooter/tests/modalFooterClassBuilder.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import modalFooterClassBuilder, { CHILDREN_CLASSES, CONTAINER_CLASSES, INFO_CLASSES } from '../modalFooterClassBuilder'; - -describe('modalFooterClassBuilder function', () => { - it('should have the common classes', () => { - const classes = modalFooterClassBuilder(); - expect(classes.containerClasses.includes(CONTAINER_CLASSES)).toBe(true); - expect(classes.childrenClasses.includes(CHILDREN_CLASSES)).toBe(true); - expect(classes.infoClasses.includes(INFO_CLASSES)).toBe(true); - }); -}); diff --git a/src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx b/src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx deleted file mode 100644 index ae88c1e..0000000 --- a/src/components/common/layout/stdModal/slots/stdModalFooter/tests/stdModalFooter.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import StdButton from '@/components/common/base/stdButton/StdButton'; -import { StdIconId } from '@/shared/utils/mappings/common/iconMaps'; -import { render, screen } from '@testing-library/react'; -import StdModalFooter, { StdFooterInformation } from '../StdModalFooter'; - -const TEST_BUTTON = ; -const TEST_BUTTONS_ARRAY = [ - , - , - , -]; -const TEST_TEXT_INFORMATION = 'Information'; -const TEST_ICON_INFORMATION = StdIconId.Info; -const TEST_INFORMATION: StdFooterInformation = { - icon: TEST_ICON_INFORMATION, - text: TEST_TEXT_INFORMATION, -}; - -describe('StdModalFooter', () => { - it('renders the default StdModalFooter', () => { - render({TEST_BUTTON}); - const footer = screen.getByRole('group'); - expect(footer).toBeInTheDocument(); - }); - - it('render the StdModalFooter with a defined number of buttons', () => { - render( - - {TEST_BUTTONS_ARRAY.map((btn, idx) => ( - {btn} - ))} - , - ); - const buttons = screen.getAllByRole('button'); - expect(buttons.length).toBe(TEST_BUTTONS_ARRAY.length); - }); - - it('render the StdModalFooter with only one button', () => { - render({TEST_BUTTON}); - const buttons = screen.getAllByRole('button'); - expect(buttons.length).toBe(1); - }); - - it('render the StdModalFooter with an information', () => { - render({TEST_BUTTON}); - expect(screen.getByRole('note')).toBeInTheDocument(); - expect(screen.getByTitle(TEST_ICON_INFORMATION)).toBeInTheDocument(); - expect(screen.getByText(TEST_TEXT_INFORMATION)).toBeInTheDocument(); - }); -}); diff --git a/src/components/common/layout/stdModal/tests/stdModal.test.tsx b/src/components/common/layout/stdModal/tests/stdModal.test.tsx deleted file mode 100644 index da99e4b..0000000 --- a/src/components/common/layout/stdModal/tests/stdModal.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import { render, screen } from '@testing-library/react'; - -import StdButton from '@/components/common/base/stdButton/StdButton'; -import StdModal from '../StdModal'; - -const onClose = vitest.fn(); -const TEST_ID = 'some-id'; -const TEST_TITLE =
Modal Title
; -const TEST_CHILDREN =
Main Content
; -const TEST_LATERAL_CHILDREN =
Lateral Content
; -const TEST_BUTTONS = [, ]; - -describe('StdModal', () => { - it('renders the StdModal with the proper id when specified', () => { - render( - - {TEST_CHILDREN} - , - ); - expect(document.querySelector(`#${TEST_ID}`)).toBeInTheDocument(); - }); - - it('render the StdModal without lateral content', () => { - render( - - {TEST_TITLE} - {TEST_CHILDREN} - {TEST_BUTTONS} - , - ); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(screen.getByRole('banner')).toBeInTheDocument(); - expect(screen.getByRole('article')).toBeInTheDocument(); - expect(screen.getByRole('group')).toBeInTheDocument(); - }); - - it('render the StdModal with lateral content ', () => { - render( - - {TEST_TITLE} - {TEST_CHILDREN} - {TEST_LATERAL_CHILDREN} - {TEST_BUTTONS} - , - ); - expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(screen.getByRole('banner')).toBeInTheDocument(); - expect(screen.getByRole('article')).toBeInTheDocument(); - expect(screen.getByRole('region')).toBeInTheDocument(); - expect(screen.getByRole('group')).toBeInTheDocument(); - }); -}); diff --git a/src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx b/src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx deleted file mode 100644 index 16ddf82..0000000 --- a/src/components/common/layout/stdModal/tests/stdModalOpener.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import StdModal from '@/components/common/layout/stdModal/StdModal'; -import StdModalOpener from '@/components/common/layout/stdModal/StdModalOpener'; -import { ModalContext } from '@/contexts/modalContext'; -import ModalProvider from '@/providers/modalProvider'; -import { noop } from '@/shared/utils/defaultUtils'; -import { render, screen } from '@testing-library/react'; -import { PropsWithChildren } from 'react'; - -const TEST_TEXT = 'My component wrapped in the modal'; -const TEST_COMPONENT = {TEST_TEXT}; -const TEST_MODAL_KEY = 'test-modal-key'; -const TEST_OTHER_MODAL_KEY = 'other-test-modal-key'; - -vi.mock('@/providers/modalProvider.tsx', () => ({ - default: ({ children }: PropsWithChildren) => ( - true }} - > - {children} - - ), -})); - -describe('useModal', () => { - it('render the expected component in the modal when the correct modal key is setted', () => { - render( - - - {TEST_COMPONENT} - - , - ); - expect(screen.getByRole('article')).toBeInTheDocument(); - }); - it('do not render the component in a modal when the wrong modal key is setted', () => { - render( - - - {TEST_COMPONENT} - - , - ); - expect(screen.queryByRole('article')).not.toBeInTheDocument(); - }); -}); diff --git a/src/index.css b/src/index.css index b5c61c9..e69de29 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/src/main.tsx b/src/main.tsx index 529debe..f2abb91 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -17,4 +17,15 @@ ReactDOM.createRoot(document.getElementById('root')!).render( , -) +); +document.documentElement.style.setProperty('--colors-primary-50', '#fafce9'); +document.documentElement.style.setProperty('--colors-primary-100', '#f3f8cf'); +document.documentElement.style.setProperty('--colors-primary-200', '#e6f2a4'); +document.documentElement.style.setProperty('--colors-primary-300', '#d3e86e'); +document.documentElement.style.setProperty('--colors-primary-400', '#bed942'); +document.documentElement.style.setProperty('--colors-primary-500', '#a4c424'); +document.documentElement.style.setProperty('--colors-primary-600', '#7c9818'); +document.documentElement.style.setProperty('--colors-primary-700', '#5e7417'); +document.documentElement.style.setProperty('--colors-primary-800', '#4b5c18'); +document.documentElement.style.setProperty('--colors-primary-900', '#404e19'); +document.documentElement.style.setProperty('--colors-primary-950', '#212b08'); diff --git a/src/pages/pegase/home/components/StudyTableDisplay.tsx b/src/pages/pegase/home/components/StudyTableDisplay.tsx index f56c923..0eb834c 100644 --- a/src/pages/pegase/home/components/StudyTableDisplay.tsx +++ b/src/pages/pegase/home/components/StudyTableDisplay.tsx @@ -3,8 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import StdSimpleTable from '@/components/common/data/stdSimpleTable/StdSimpleTable'; + import { useState } from 'react'; +import StdSimpleTable from '@/components/common/data/stdSimpleTable/StdSimpleTable'; import { StudyDTO } from '@/shared/types/index'; import getStudyTableHeaders from './StudyTableHeaders'; import { addSortColumn, useNewStudyModal } from './StudyTableUtils'; @@ -13,8 +14,8 @@ import { RowSelectionState } from '@tanstack/react-table'; import StdButton from '@/components/common/base/stdButton/StdButton'; import { StudyStatus } from '@/shared/types/common/StudyStatus.type'; import { useStudyTableDisplay } from './useStudyTableDisplay'; -import StdModal from '@/components/common/layout/stdModal/StdModal'; import { useTranslation } from 'react-i18next'; +import StudyCreationModal from '../../studies/StudyCreationModal'; interface StudyTableDisplayProps { searchStudy: string | undefined; @@ -28,12 +29,14 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = const [isHeaderHovered, setIsHeaderHovered] = useState(false); const { isModalOpen, toggleModal } = useNewStudyModal(); const { t } = useTranslation(); + const [selectedStudy, setSelectedStudy] = useState(null); const handleSort = (column: string) => { const newSortOrder = sortByState[column] === 'asc' ? 'desc' : 'asc'; setSortByState({ [column]: newSortOrder }); setSortedColumn(column); }; + const handleHeaderHover = (hovered: boolean) => { setIsHeaderHovered(hovered); }; @@ -62,6 +65,12 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = const isDuplicateActive = selectedStatus === StudyStatus.GENERATED; const isDeleteActive = selectedStatus === StudyStatus.ERROR || selectedStatus === StudyStatus.IN_PROGRESS; + const handleDuplicate = () => { + const selectedStudy = rows[Number.parseInt(selectedRowId || '-1')]; + setSelectedStudy(selectedStudy); + toggleModal(); + }; + return (
@@ -87,12 +96,7 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) =
{selectedRowId !== undefined ? ( <> - console.log('duplicate')} - variant="outlined" - disabled={!isDuplicateActive} - /> + console.log('Delete')} @@ -104,17 +108,7 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) = ) : ( )} - {isModalOpen && ( - - {t('home.@new_study')} - -

Here you can create a new study. Add your content here.

-
- - - -
- )} + {isModalOpen && }
diff --git a/src/pages/pegase/home/components/studyService.ts b/src/pages/pegase/home/components/studyService.ts new file mode 100644 index 0000000..2a937d1 --- /dev/null +++ b/src/pages/pegase/home/components/studyService.ts @@ -0,0 +1,51 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { notifyToast } from '@/shared/notification/notification'; + +interface StudyData { + name: string; + createdBy: string; + keywords: string[]; + project: string; + horizon: string; + trajectoryIds: number[]; +} + +export const saveStudy = async (studyData: StudyData, toggleModal: () => void) => { + try { + const response = await fetch('http://localhost:8093/v1/study', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(studyData), + }); + if (!response.ok) { + const errorText = await response.text(); + const errorData = JSON.parse(errorText); + throw new Error(`${errorData.message || errorText}`); + } + notifyToast({ + type: 'success', + message: 'Study created successfully', + }); + toggleModal(); + } catch (error: any) { + notifyToast({ + type: 'error', + message: `${error.message}`, + }); + } +}; +export const fetchSuggestedKeywords = async (query: string): Promise => { + const response = await fetch(`http://localhost:8093/v1/study/keywords/search?partialName=${query}`); + if (!response.ok) { + throw new Error('Failed to fetch suggested keywords'); + } + const data = await response.json(); + return data; +}; diff --git a/src/pages/pegase/home/components/useStudyTableDisplay.ts b/src/pages/pegase/home/components/useStudyTableDisplay.ts index 7ffc6b5..4bccf53 100644 --- a/src/pages/pegase/home/components/useStudyTableDisplay.ts +++ b/src/pages/pegase/home/components/useStudyTableDisplay.ts @@ -8,7 +8,7 @@ import { useEffect, useState } from 'react'; import { StudyDTO } from '@/shared/types/index'; import { getEnvVariables } from '@/envVariables'; -const ITEMS_PER_PAGE = 5; +const ITEMS_PER_PAGE = 10; const BASE_URL = getEnvVariables('VITE_BACK_END_BASE_URL'); const PAGINATION_CURRENT = 0; const PAGINATION_COUNT = 0; diff --git a/src/pages/pegase/studies/HorizonInput.tsx b/src/pages/pegase/studies/HorizonInput.tsx new file mode 100644 index 0000000..90db5f4 --- /dev/null +++ b/src/pages/pegase/studies/HorizonInput.tsx @@ -0,0 +1,106 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import React, { useState } from 'react'; +import { RdsButton, RdsIconId, RdsInputText } from 'rte-design-system-react'; + +interface YearDropdownProps { + value: string; + onChange: (value: string) => void; +} + +const generateYears = (startYear: number, endYear: number): number[] => { + const years = []; + for (let year = startYear; year <= endYear; year++) { + years.push(year); + } + return years; +}; + +const HorizonInput: React.FC = ({ value, onChange }) => { + const [isOpen, setIsOpen] = useState(false); // State to control dropdown visibility + const [errorMessage, setErrorMessage] = useState(''); // State for error message + + const currentYear = new Date().getFullYear(); + const years = generateYears(currentYear, 2050); + + const toggleDropdown = () => { + setIsOpen(!isOpen); // Toggle dropdown visibility + }; + + const handleBlur = () => { + // If the entered value is not in the list, show an error and clear it + if (!years.includes(parseInt(value))) { + setErrorMessage('Veuillez choisir une date valide.'); + onChange(''); + } else { + setErrorMessage(''); // Clear error if the value is valid + } + }; + + return ( +
+ {/* Container for input and button */} +
+ {/* Input field */} + { + onChange(t || ''); + setErrorMessage(''); // Clear error message on input change + }} + onBlur={handleBlur} // Validate input on blur + placeHolder="Select a horizon" + variant="outlined" + /> + + {/* Toggle Button */} + +
+ + {/* Error Message */} + {errorMessage &&
{errorMessage}
} + + {/* Dropdown list */} + {isOpen && ( +
e.preventDefault()} // Prevent dropdown from closing when clicking inside + > + {years.map((year, index) => ( +
{ + onChange(year.toString()); + setIsOpen(false); // Close the dropdown on selection + setErrorMessage(''); // Clear error on valid selection + }} + > + {year} +
+ ))} +
+ )} +
+ ); +}; + +export default HorizonInput; diff --git a/src/pages/pegase/studies/KeywordsInput.tsx b/src/pages/pegase/studies/KeywordsInput.tsx new file mode 100644 index 0000000..76becba --- /dev/null +++ b/src/pages/pegase/studies/KeywordsInput.tsx @@ -0,0 +1,136 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import { useState } from 'react'; +import { RdsButton, RdsIcon, RdsIconId, RdsInputText } from 'rte-design-system-react'; +import { fetchSuggestedKeywords } from '@/pages/pegase/home/components/studyService'; + +const MAX_KEYWORDS = 6; + +interface KeywordsInputProps { + keywords: string[]; + setKeywords: React.Dispatch>; +} + +const KeywordsInput: React.FC = ({ keywords, setKeywords }) => { + const [keywordInput, setKeywordInput] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [suggestedKeywords, setSuggestedKeywords] = useState([]); + + const handleKeywordChange = async (value: string) => { + setKeywordInput(value); + setErrorMessage(''); // Clear error message when input changes + try { + const tags = await fetchSuggestedKeywords(value); + setSuggestedKeywords(tags); + } catch (error) { + setErrorMessage('Failed to fetch suggested keywords'); + } + }; + + const handleAddKeyword = (suggestedKeyword = keywordInput) => { + if (suggestedKeyword.trim()) { + if (keywords.includes(suggestedKeyword.trim())) { + setErrorMessage('Keyword already exists'); + } else if (suggestedKeyword.trim().length < 3 || suggestedKeyword.trim().length > 10) { + setErrorMessage('Keyword must be between 3 and 10 characters'); + } else if (keywords.length >= MAX_KEYWORDS) { + setErrorMessage('Cannot add more than 6 keywords'); + } else { + setKeywords((prevKeywords) => [...prevKeywords, suggestedKeyword.trim()]); + setKeywordInput(''); + setErrorMessage(''); + } + } + }; + + const clearAllKeywords = () => { + setKeywords([]); + }; + + const handleRemoveKeyword = (index: number) => { + setKeywords((prevKeywords) => prevKeywords.filter((_, i) => i !== index)); + }; + + return ( +
+
+
+ + {keywordInput && keywordInput.length >= 3 && ( + handleAddKeyword()} + icon={RdsIconId.Add} + color="secondary" + size="extraSmall" + variant="transparent" + /> + )} +
+ + {/* Suggested Keywords Dropdown */} + {keywordInput && ( +
e.preventDefault()} // Prevent closing when interacting with dropdown + > + {suggestedKeywords.map((suggestedKeyword, index) => ( +
handleAddKeyword(suggestedKeyword)} + > + {suggestedKeyword} +
+ ))} +
+ )} +
+ + {/* Error Message */} + {errorMessage &&
{errorMessage}
} + + {/* Keywords Display and Clear All Button */} +
+ {keywords.map((keyword, index) => ( +
+ {keyword} + handleRemoveKeyword(index)} + size="extraSmall" + variant="text" + color="secondary" + /> +
+ ))} +
+ + {/* Clear All Keywords Button */} + {keywords.length > 0 && ( +
+ + Clear all +
+ )} +
+ ); +}; + +export default KeywordsInput; diff --git a/src/pages/pegase/studies/ProjectInput.tsx b/src/pages/pegase/studies/ProjectInput.tsx new file mode 100644 index 0000000..2af0017 --- /dev/null +++ b/src/pages/pegase/studies/ProjectInput.tsx @@ -0,0 +1,92 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +// src/components/ProjectInput.tsx +import React, { useState, useEffect } from 'react'; +import { RdsInputText } from 'rte-design-system-react'; + +interface ProjectManagerProps { + value: string; + onChange: (value: string) => void; +} + +const fetchProjects = async (query: string): Promise => { + const response = await fetch(`http://localhost:8093/v1/project/autocomplete?partialName=${query}`); + if (!response.ok) { + throw new Error('Failed to fetch projects'); + } + const data = await response.json(); + return data.map((project: { name: string }) => project.name); // Extract and return only the 'name' property +}; + +const ProjectInput: React.FC = ({ value, onChange }) => { + const [projects, setProjects] = useState([]); + const [errorMessage, setErrorMessage] = useState(''); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + useEffect(() => { + const loadProjects = async () => { + try { + const projectList = await fetchProjects(value); + setProjects(projectList); + } catch (error) { + setErrorMessage('Failed to fetch projects'); + } + }; + + if (value) { + loadProjects(); + } else { + setProjects([]); + } + }, [value]); + + const handleProjectSelect = (project: string) => { + onChange(project); + setIsDropdownOpen(false); + }; + + return ( +
+ { + onChange(t || ''); + setIsDropdownOpen(true); + }} + placeHolder="Select or add a project" + variant="outlined" + /> + {isDropdownOpen && projects.length > 0 && ( +
e.preventDefault()} // Prevent dropdown from closing when clicking inside + > + {projects.map((project, index) => ( +
handleProjectSelect(project)} + > + {project} +
+ ))} +
+ )} + {errorMessage &&
{errorMessage}
} +
+ ); +}; + +export default ProjectInput; diff --git a/src/pages/pegase/studies/StudyCreationModal.tsx b/src/pages/pegase/studies/StudyCreationModal.tsx new file mode 100644 index 0000000..7a320e0 --- /dev/null +++ b/src/pages/pegase/studies/StudyCreationModal.tsx @@ -0,0 +1,121 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +import React, { useState, useEffect } from 'react'; +import { RdsButton, RdsIconId, RdsInputText, RdsModal } from 'rte-design-system-react'; +import { useTranslation } from 'react-i18next'; +import KeywordsInput from '@/pages/pegase/studies/KeywordsInput'; +import HorizonInput from '@/pages/pegase/studies/HorizonInput'; +import ProjectInput from '@/pages/pegase/studies/ProjectInput'; +import { saveStudy } from '@/pages/pegase/home/components/studyService'; +import { StudyDTO } from '@/shared/types/index'; + +interface StudyCreationModalProps { + isOpen: boolean; + onClose: () => void; + study?: StudyDTO | null; +} + +const StudyCreationModal: React.FC = ({ onClose, study }) => { + const { t } = useTranslation(); + const [studyName, setStudyName] = useState(''); + const [horizon, setHorizon] = useState(''); + const [projectName, setProjectName] = useState(study?.project || ''); + const [keywords, setKeywords] = useState(study?.keywords || []); + const [trajectoryIds] = useState(study?.trajectoryIds || []); + const [isFormValid, setIsFormValid] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const saveStudyHandler = async () => { + const studyData = { + name: studyName, + createdBy: 'currentUser', // Replace with the actual user identifier + keywords: keywords, + project: projectName, + horizon: horizon, + trajectoryIds: trajectoryIds, + }; + + await saveStudy(studyData, onClose); + // Clear form fields + setStudyName(''); + setProjectName(''); + setHorizon(''); + setKeywords([]); + }; + + const validateForm = () => { + if (studyName && projectName && horizon && keywords.length > 0 && !errorMessage) { + setIsFormValid(true); + } else { + setIsFormValid(false); + } + }; + + useEffect(() => { + validateForm(); + }, [studyName, projectName, horizon, keywords, errorMessage]); + + const validateHorizon = (year: string) => { + const currentYear = new Date().getFullYear(); + const selectedYear = parseInt(year); + if (selectedYear < currentYear) { + setErrorMessage('Horizon must be a year greater than the current year'); + } else if (selectedYear > 2100) { + setErrorMessage('Horizon must be a year less than or equal to 2100'); + } else { + setErrorMessage(''); + } + }; + + const handleHorizonChange = (value: string) => { + const numericRegex = /^[0-9]*$/; + if (!numericRegex.test(value)) { + setErrorMessage('Please enter a valid year'); + setHorizon(''); + } else { + setHorizon(value); + validateHorizon(value); + } + }; + + return ( + + {study ? t('home.@duplicate_study') : t('home.@new_study')} + +
+
+ setStudyName(t || '')} + variant="outlined" + placeHolder="Name your study..." + /> +
+
+ +
+
+ + +
+ + + + +
+ ); +}; + +export default StudyCreationModal; diff --git a/src/shared/i18n/en.json b/src/shared/i18n/en.json index bbfa688..aeb6473 100644 --- a/src/shared/i18n/en.json +++ b/src/shared/i18n/en.json @@ -38,7 +38,8 @@ "@project": "Project", "@my_studies": "My studies", "@my_projects": "My projects", - "@new_study": "New Study" + "@new_study": "Create a study", + "@duplicate_study": "Duplicate a study" }, "project": { "@setting": "Setting", diff --git a/src/shared/types/pegase/study.ts b/src/shared/types/pegase/study.ts index 00b498c..27e5462 100644 --- a/src/shared/types/pegase/study.ts +++ b/src/shared/types/pegase/study.ts @@ -13,4 +13,5 @@ export type StudyDTO = { project: string; status: string; horizon: string; + trajectoryIds: number[]; }; diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo index dad9bf6..1559713 100644 --- a/tsconfig.tsbuildinfo +++ b/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/envVariables.ts","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/testSetup.ts","./src/vite-env.d.ts","./src/components/common/base/StdTagList/StdTagList.tsx","./src/components/common/base/StdTagList/tagListClassBuilder.ts","./src/components/common/base/StdTagList/tests/stdTagList.test.tsx","./src/components/common/base/StdTagList/tests/tagListClassBuilder.test.ts","./src/components/common/base/stdButton/StdButton.tsx","./src/components/common/base/stdButton/buttonClassBuilder.ts","./src/components/common/base/stdButton/tests/buttonClassBuilder.test.ts","./src/components/common/base/stdButton/tests/stdButton.test.tsx","./src/components/common/base/stdChip/StdChip.tsx","./src/components/common/base/stdChip/chipClassBuilder.ts","./src/components/common/base/stdChip/tests/chipClassBuilder.test.ts","./src/components/common/base/stdChip/tests/stdChip.test.tsx","./src/components/common/base/stdIcon/Icon.tsx","./src/components/common/base/stdIcon/StdIcon.tsx","./src/components/common/base/stdIcon/iconClassBuilder.ts","./src/components/common/base/stdIcon/tests/iconClassBuilder.test.ts","./src/components/common/base/stdIcon/tests/stdIcon.test.tsx","./src/components/common/base/stdIconButton/StdIconButton.tsx","./src/components/common/base/stdIconButton/iconButtonClassBuilder.ts","./src/components/common/base/stdIconButton/tests/StdIconButton.test.tsx","./src/components/common/base/stdIconButton/tests/iconButtonClassBuilder.test.ts","./src/components/common/base/stdTag/StdTag.tsx","./src/components/common/base/stdTag/tagClassBuilder.ts","./src/components/common/base/stdTag/tests/stdTag.test.tsx","./src/components/common/base/stdTag/tests/tagClassBuilder.test.ts","./src/components/common/data/stdSimpleTable/StdSimpleTable.tsx","./src/components/common/data/stdSimpleTable/tests/stdSimpleTable.test.tsx","./src/components/common/data/stdTable/TableContext.tsx","./src/components/common/data/stdTable/TableCore.tsx","./src/components/common/data/stdTable/tableCoreRowClassBuilder.ts","./src/components/common/data/stdTable/useTableContext.ts","./src/components/common/data/stdTable/cells/ExpandableCell.tsx","./src/components/common/data/stdTable/cells/tests/expandableCell.test.tsx","./src/components/common/data/stdTable/features/readOnly.ts","./src/components/common/data/stdTable/lineRender/StdCollapseIcon.tsx","./src/components/common/data/stdTable/lineRender/tests/stdCollapseIcon.test.tsx","./src/components/common/data/stdTable/tests/TableCore.test.tsx","./src/components/common/data/stdTable/tests/tableCoreRowClassBuilder.test.ts","./src/components/common/data/stdTable/tests/testTableUtils.ts","./src/components/common/data/stdTable/types/readOnly.type.d.ts","./src/components/common/data/stdTable/types/sizeClassNames.d.ts","./src/components/common/data/stdTablePagination/StdTablePagination.tsx","./src/components/common/data/stdTablePagination/tests/stdTablePagination.test.tsx","./src/components/common/forms/stdCheckbox/StdCheckbox.tsx","./src/components/common/forms/stdCheckbox/checkboxClassBuilder.ts","./src/components/common/forms/stdCheckbox/tests/StdCheckbox.test.tsx","./src/components/common/forms/stdCheckbox/tests/checkboxClassBuilder.test.ts","./src/components/common/forms/stdRadioButton/StdRadioButton.tsx","./src/components/common/forms/stdRadioButton/radioButtonClassBuilder.ts","./src/components/common/forms/stdRadioButton/tests/radioButtonClassBuilder.test.ts","./src/components/common/forms/stdRadioButton/tests/stdRadioButton.test.tsx","./src/components/common/forms/stdRadioButtonGroup/StdRadioButtonGroup.tsx","./src/components/common/forms/stdRadioButtonGroup/radioButtonGroupClassBuilder.ts","./src/components/common/forms/stdRadioButtonGroup/tests/radioButtonGroupClassBuilder.test.ts","./src/components/common/forms/stdRadioButtonGroup/tests/stdRadioButtonGroup.test.tsx","./src/components/common/forms/stdRequiredIndicator/StdRequiredIndicator.tsx","./src/components/common/forms/stdSearchInput/SearchInputClassBuilder.ts","./src/components/common/forms/stdSearchInput/StdSearchInput.tsx","./src/components/common/forms/stdSearchInput/tests/SearchInput.test.tsx","./src/components/common/forms/stdSearchInput/tests/searchInputClassBuilder.test.ts","./src/components/common/forms/stdSwitch/StdSwitch.tsx","./src/components/common/forms/stdSwitch/SwitchClassBuilder.ts","./src/components/common/forms/stdSwitch/stdSearchInput/SearchInputClassBuilder.ts","./src/components/common/forms/stdSwitch/stdSearchInput/StdSearchInput.tsx","./src/components/common/forms/stdSwitch/stdSearchInput/tests/SearchInput.test.tsx","./src/components/common/forms/stdSwitch/stdSearchInput/tests/searchInputClassBuilder.test.ts","./src/components/common/forms/stdSwitch/tests/StdSwitch.test.tsx","./src/components/common/forms/stdSwitch/tests/switchClassBuilder.test.ts","./src/components/common/handler/ThemeHandler.tsx","./src/components/common/handler/test/ThemeHandler.test.tsx","./src/components/common/layout/stdAlert/StdAlert.tsx","./src/components/common/layout/stdAlert/alertClassBuilder.ts","./src/components/common/layout/stdAlert/tests/alertClassBuilder.test.ts","./src/components/common/layout/stdAlert/tests/stdAlert.test.tsx","./src/components/common/layout/stdAvatar/StdAvatar.tsx","./src/components/common/layout/stdAvatar/avatarClassBuilder.ts","./src/components/common/layout/stdAvatar/tests/StdAvatar.test.tsx","./src/components/common/layout/stdAvatar/tests/avatarClassBuilder.test.ts","./src/components/common/layout/stdAvatarGroup/StdAvatarGroup.tsx","./src/components/common/layout/stdAvatarGroup/avatarGroupClassBuilder.ts","./src/components/common/layout/stdAvatarGroup/avatarTools.ts","./src/components/common/layout/stdAvatarGroup/tests/StdAvatarGroup.test.tsx","./src/components/common/layout/stdAvatarGroup/tests/avatarTools.test.ts","./src/components/common/layout/stdBanner/StdBanner.tsx","./src/components/common/layout/stdBanner/bannerClassBuilder.ts","./src/components/common/layout/stdBanner/tests/bannerClassBuilder.test.ts","./src/components/common/layout/stdBanner/tests/stdBanner.test.tsx","./src/components/common/layout/stdCard/StdCard.tsx","./src/components/common/layout/stdCard/cardClassBuilder.ts","./src/components/common/layout/stdCard/tests/cardClassBuilder.test.ts","./src/components/common/layout/stdCard/tests/stdCard.test.tsx","./src/components/common/layout/stdCard/tests/stdCard.tsx","./src/components/common/layout/stdDivider/StdDivider.tsx","./src/components/common/layout/stdDivider/dividerClassBuilder.ts","./src/components/common/layout/stdDivider/tests/StdDivider.test.tsx","./src/components/common/layout/stdDivider/tests/dividerClassBuilder.test.ts","./src/components/common/layout/stdDropdown/StdDropdown.tsx","./src/components/common/layout/stdDropdown/StdDropdownWithHeader.tsx","./src/components/common/layout/stdDropdown/dropdownClassBuilder.ts","./src/components/common/layout/stdDropdown/subComponents/StdDropdownAddFooter.tsx","./src/components/common/layout/stdDropdown/subComponents/StdDropdownHeader.tsx","./src/components/common/layout/stdDropdown/subComponents/StdDropdownItem.tsx","./src/components/common/layout/stdDropdown/subComponents/StdDropdownSearchHeader.tsx","./src/components/common/layout/stdDropdown/subComponents/StdEmptySearch.tsx","./src/components/common/layout/stdDropdown/test/StdDropdown.test.tsx","./src/components/common/layout/stdDropdown/test/StdDropdownItem.test.tsx","./src/components/common/layout/stdDropdown/test/dropdownClassBuilder.test.ts","./src/components/common/layout/stdFloatingWrapper/StdFloatingWrapper.tsx","./src/components/common/layout/stdFloatingWrapper/slots/stdFloatingElement/StdFloatingElement.tsx","./src/components/common/layout/stdFloatingWrapper/slots/stdFloatingTrigger/StdFloatingTrigger.tsx","./src/components/common/layout/stdFloatingWrapper/tests/stdFloatingWrapper.test.tsx","./src/components/common/layout/stdHeading/StdHeading.tsx","./src/components/common/layout/stdHeading/headingClassBuilder.ts","./src/components/common/layout/stdHeading/tests/StdHeading.test.tsx","./src/components/common/layout/stdHeading/tests/headingClassBuilder.test.ts","./src/components/common/layout/stdNavbar/StdNavbar.tsx","./src/components/common/layout/stdNavbar/StdNavbarController.tsx","./src/components/common/layout/stdNavbar/StdNavbarHeader.tsx","./src/components/common/layout/stdNavbar/StdNavbarMenu.tsx","./src/components/common/layout/stdNavbar/StdNavbarMenuItem.tsx","./src/components/common/layout/stdNavbar/navbarClassBuilder.ts","./src/components/common/layout/stdNavbar/tests/StdNavbar.test.tsx","./src/components/common/layout/stdNavbar/tests/StdNavbarController.test.tsx","./src/components/common/layout/stdNavbar/tests/StdNavbarHeader.test.tsx","./src/components/common/layout/stdNavbar/tests/StdNavbarMenuItem.test.tsx","./src/components/common/layout/stdNavbar/tests/navbarClassBuilder.test.ts","./src/components/common/layout/stdPopover/StdPopover.tsx","./src/components/common/layout/stdPopover/StdPopoverContent.tsx","./src/components/common/layout/stdPopover/StdPopoverFooter.tsx","./src/components/common/layout/stdPopover/StdPopoverTrigger.tsx","./src/components/common/layout/stdPopover/tests/stdPopover.test.tsx","./src/components/common/layout/stdTextTooltip/StdTextTooltip.tsx","./src/components/common/layout/stdTextTooltip/tests/stdTooltipText.test.tsx","./src/components/common/layout/stdTextWithTooltip/StdTextWithTooltip.tsx","./src/components/common/layout/stdToast/StdToast.tsx","./src/components/common/layout/stdToast/toastClassBuilder.ts","./src/components/common/layout/stdToast/tests/stdToast.test.tsx","./src/components/common/layout/stdToast/tests/toastClassBuilder.test.ts","./src/components/common/layout/stdTooltip/StdTooltip.tsx","./src/components/common/layout/stdTooltip/tests/StdTooltip.test.tsx","./src/components/pegase/header/Header.tsx","./src/components/pegase/navbar/Navbar.tsx","./src/components/pegase/pegaseCard/cardClassBuilder.ts","./src/components/pegase/pegaseCard/pegaseCard.tsx","./src/components/pegase/pegaseCard/useDropdownOptions.ts","./src/components/pegase/pegaseCard/pegaseCardTitle/cardTitleClassBuilder.tsx","./src/components/pegase/pegaseCard/pegaseCardTitle/pegaseCardTitle.tsx","./src/components/pegase/pegaseCard/pegaseCardTitle/tests/cardTitleClassBuilder.test.ts","./src/components/pegase/pegaseCard/pegaseCardTitle/tests/pegaseCardTitle.test.tsx","./src/components/pegase/pegaseCard/tests/cardTripleActionClassBuilder.test.ts","./src/components/pegase/pegaseCard/tests/pegaseCardTripleAction.test.tsx","./src/components/pegase/star/PegaseStar.tsx","./src/contexts/UserContext.tsx","./src/contexts/createFastContext.tsx","./src/contexts/modalContext.ts","./src/hooks/useDateFormatter.ts","./src/hooks/useInputFormState.ts","./src/hooks/useModal.ts","./src/hooks/useProjectNavigation.ts","./src/hooks/useStdId.ts","./src/hooks/common/useActiveKeyboard.ts","./src/hooks/common/useCallOnResize.ts","./src/hooks/common/useDebounce.ts","./src/hooks/common/useInputFormState.ts","./src/hooks/common/usePrevious.ts","./src/hooks/common/useStdId.ts","./src/hooks/common/useTimeout.ts","./src/hooks/common/test/useCallOnResize.test.ts","./src/hooks/common/test/useDebounce.test.ts","./src/hooks/common/test/useInputFormState.test.ts","./src/hooks/common/test/usePrevious.test.ts","./src/hooks/common/test/useStdId.test.ts","./src/hooks/common/test/useTimeout.test.ts","./src/mocks/mockTools.ts","./src/mocks/data/components/dropdownItems.mock.ts","./src/mocks/data/components/navbarHeader.ts","./src/mocks/data/features/menuItemData.mock.tsx","./src/mocks/data/list/keywords.ts","./src/mocks/data/list/names.ts","./src/mocks/data/list/projectName.ts","./src/mocks/data/list/studyName.ts","./src/mocks/data/list/user.mocks.ts","./src/mocks/data/list/user.ts","./src/pages/pegase/antares/Antares.tsx","./src/pages/pegase/home/HomePage.tsx","./src/pages/pegase/home/components/HomePageContent.tsx","./src/pages/pegase/home/components/SearchBar.tsx","./src/pages/pegase/home/components/StudiesPagination.tsx","./src/pages/pegase/home/components/StudyTableDisplay.tsx","./src/pages/pegase/home/components/useStudyTableDisplay.ts","./src/pages/pegase/home/components/tests/useStudyTableDisplay.test.ts","./src/pages/pegase/home/pinnedProjects/PinnedProject.tsx","./src/pages/pegase/home/pinnedProjects/PinnedProjectCard.tsx","./src/pages/pegase/home/pinnedProjects/ProjectCreator.tsx","./src/pages/pegase/home/pinnedProjects/tests/handleUnpin.test.ts","./src/pages/pegase/logout/Logout.tsx","./src/pages/pegase/projects/ProjectContent.tsx","./src/pages/pegase/projects/Projects.tsx","./src/pages/pegase/projects/ProjectsPagination.tsx","./src/pages/pegase/projects/projectService.test.tsx","./src/pages/pegase/projects/projectService.ts","./src/pages/pegase/projects/projectDetails/ProjectDetails.tsx","./src/pages/pegase/projects/projectDetails/ProjectDetailsContent.tsx","./src/pages/pegase/projects/projectDetails/ProjectDetailsHeader.tsx","./src/pages/pegase/projects/projectDetails/fetchProjectDetails.test.tsx","./src/pages/pegase/reports/LogsPage.tsx","./src/pages/pegase/settings/Settings.tsx","./src/shared/constants.ts","./src/shared/notification/containers.tsx","./src/shared/notification/notification.tsx","./src/shared/types/index.ts","./src/shared/types/common/DisplayStatus.type.ts","./src/shared/types/common/MenuNavItem.type.ts","./src/shared/types/common/StdBase.type.ts","./src/shared/types/common/Tailwind.type.ts","./src/shared/types/common/TailwindColorClass.type.ts","./src/shared/types/common/User.type.ts","./src/shared/types/common/UserSettings.type.ts","./src/shared/types/common/tests/testUtils.tsx","./src/shared/types/pegase/Project.type.ts","./src/shared/types/pegase/study.ts","./src/shared/utils/dateFormatter.ts","./src/shared/utils/tabIndexUtils.ts","./src/shared/utils/common/defaultUtils.ts","./src/shared/utils/common/displayUtils.ts","./src/shared/utils/common/slotsUtils.ts","./src/shared/utils/common/classes/classMerger.ts","./src/shared/utils/common/classes/test/classMerger.test.ts","./src/shared/utils/common/dom/getDimensions.ts","./src/shared/utils/common/dom/test/getDimensions.test.tsx","./src/shared/utils/common/mappings/iconMaps.ts","./tailwind.config.ts","./vite-env.d.ts"],"version":"5.6.2"} \ No newline at end of file +{"root":["./src/App.tsx","./src/envVariables.ts","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/testSetup.ts","./src/vite-env.d.ts","./src/components/common/base/StdTagList/StdTagList.tsx","./src/components/common/base/StdTagList/tagListClassBuilder.ts","./src/components/common/base/StdTagList/tests/stdTagList.test.tsx","./src/components/common/base/StdTagList/tests/tagListClassBuilder.test.ts","./src/components/common/base/stdButton/StdButton.tsx","./src/components/common/base/stdButton/buttonClassBuilder.ts","./src/components/common/base/stdButton/tests/buttonClassBuilder.test.ts","./src/components/common/base/stdButton/tests/stdButton.test.tsx","./src/components/common/base/stdChip/StdChip.tsx","./src/components/common/base/stdChip/chipClassBuilder.ts","./src/components/common/base/stdChip/tests/chipClassBuilder.test.ts","./src/components/common/base/stdChip/tests/stdChip.test.tsx","./src/components/common/base/stdIcon/Icon.tsx","./src/components/common/base/stdIcon/StdIcon.tsx","./src/components/common/base/stdIcon/iconClassBuilder.ts","./src/components/common/base/stdIcon/tests/iconClassBuilder.test.ts","./src/components/common/base/stdIcon/tests/stdIcon.test.tsx","./src/components/common/base/stdIconButton/StdIconButton.tsx","./src/components/common/base/stdIconButton/iconButtonClassBuilder.ts","./src/components/common/base/stdIconButton/tests/StdIconButton.test.tsx","./src/components/common/base/stdIconButton/tests/iconButtonClassBuilder.test.ts","./src/components/common/base/stdTag/StdTag.tsx","./src/components/common/base/stdTag/tagClassBuilder.ts","./src/components/common/base/stdTag/tests/stdTag.test.tsx","./src/components/common/base/stdTag/tests/tagClassBuilder.test.ts","./src/components/common/data/stdSimpleTable/StdSimpleTable.tsx","./src/components/common/data/stdSimpleTable/tests/stdSimpleTable.test.tsx","./src/components/common/data/stdTable/TableContext.tsx","./src/components/common/data/stdTable/TableCore.tsx","./src/components/common/data/stdTable/tableCoreRowClassBuilder.ts","./src/components/common/data/stdTable/useTableContext.ts","./src/components/common/data/stdTable/cells/ExpandableCell.tsx","./src/components/common/data/stdTable/cells/tests/expandableCell.test.tsx","./src/components/common/data/stdTable/features/readOnly.ts","./src/components/common/data/stdTable/lineRender/StdCollapseIcon.tsx","./src/components/common/data/stdTable/lineRender/tests/stdCollapseIcon.test.tsx","./src/components/common/data/stdTable/tests/TableCore.test.tsx","./src/components/common/data/stdTable/tests/tableCoreRowClassBuilder.test.ts","./src/components/common/data/stdTable/tests/testTableUtils.ts","./src/components/common/data/stdTable/types/readOnly.type.d.ts","./src/components/common/data/stdTable/types/sizeClassNames.d.ts","./src/components/common/data/stdTablePagination/StdTablePagination.tsx","./src/components/common/data/stdTablePagination/tests/stdTablePagination.test.tsx","./src/components/common/forms/stdCheckbox/StdCheckbox.tsx","./src/components/common/forms/stdCheckbox/checkboxClassBuilder.ts","./src/components/common/forms/stdCheckbox/tests/StdCheckbox.test.tsx","./src/components/common/forms/stdCheckbox/tests/checkboxClassBuilder.test.ts","./src/components/common/forms/stdInputText/StdInputText.tsx","./src/components/common/forms/stdInputText/textClassBuilder.ts","./src/components/common/forms/stdInputText/tests/stdInputText.test.tsx","./src/components/common/forms/stdInputText/tests/textClassBuilder.test.ts","./src/components/common/forms/stdRadioButton/StdRadioButton.tsx","./src/components/common/forms/stdRadioButton/radioButtonClassBuilder.ts","./src/components/common/forms/stdRadioButton/tests/radioButtonClassBuilder.test.ts","./src/components/common/forms/stdRadioButton/tests/stdRadioButton.test.tsx","./src/components/common/forms/stdRadioButtonGroup/StdRadioButtonGroup.tsx","./src/components/common/forms/stdRadioButtonGroup/radioButtonGroupClassBuilder.ts","./src/components/common/forms/stdRadioButtonGroup/tests/radioButtonGroupClassBuilder.test.ts","./src/components/common/forms/stdRadioButtonGroup/tests/stdRadioButtonGroup.test.tsx","./src/components/common/forms/stdRequiredIndicator/StdRequiredIndicator.tsx","./src/components/common/forms/stdSearchInput/SearchInputClassBuilder.ts","./src/components/common/forms/stdSearchInput/StdSearchInput.tsx","./src/components/common/forms/stdSearchInput/tests/SearchInput.test.tsx","./src/components/common/forms/stdSearchInput/tests/searchInputClassBuilder.test.ts","./src/components/common/forms/stdSwitch/StdSwitch.tsx","./src/components/common/forms/stdSwitch/SwitchClassBuilder.ts","./src/components/common/forms/stdSwitch/stdSearchInput/SearchInputClassBuilder.ts","./src/components/common/forms/stdSwitch/stdSearchInput/StdSearchInput.tsx","./src/components/common/forms/stdSwitch/stdSearchInput/tests/SearchInput.test.tsx","./src/components/common/forms/stdSwitch/stdSearchInput/tests/searchInputClassBuilder.test.ts","./src/components/common/forms/stdSwitch/tests/StdSwitch.test.tsx","./src/components/common/forms/stdSwitch/tests/switchClassBuilder.test.ts","./src/components/common/handler/ThemeHandler.tsx","./src/components/common/handler/test/ThemeHandler.test.tsx","./src/components/common/layout/stdAlert/StdAlert.tsx","./src/components/common/layout/stdAlert/alertClassBuilder.ts","./src/components/common/layout/stdAlert/tests/alertClassBuilder.test.ts","./src/components/common/layout/stdAlert/tests/stdAlert.test.tsx","./src/components/common/layout/stdAvatar/StdAvatar.tsx","./src/components/common/layout/stdAvatar/avatarClassBuilder.ts","./src/components/common/layout/stdAvatar/tests/StdAvatar.test.tsx","./src/components/common/layout/stdAvatar/tests/avatarClassBuilder.test.ts","./src/components/common/layout/stdAvatarGroup/StdAvatarGroup.tsx","./src/components/common/layout/stdAvatarGroup/avatarGroupClassBuilder.ts","./src/components/common/layout/stdAvatarGroup/avatarTools.ts","./src/components/common/layout/stdAvatarGroup/tests/StdAvatarGroup.test.tsx","./src/components/common/layout/stdAvatarGroup/tests/avatarTools.test.ts","./src/components/common/layout/stdBanner/StdBanner.tsx","./src/components/common/layout/stdBanner/bannerClassBuilder.ts","./src/components/common/layout/stdBanner/tests/bannerClassBuilder.test.ts","./src/components/common/layout/stdBanner/tests/stdBanner.test.tsx","./src/components/common/layout/stdCard/StdCard.tsx","./src/components/common/layout/stdCard/cardClassBuilder.ts","./src/components/common/layout/stdCard/tests/cardClassBuilder.test.ts","./src/components/common/layout/stdCard/tests/stdCard.test.tsx","./src/components/common/layout/stdCard/tests/stdCard.tsx","./src/components/common/layout/stdDivider/StdDivider.tsx","./src/components/common/layout/stdDivider/dividerClassBuilder.ts","./src/components/common/layout/stdDivider/tests/StdDivider.test.tsx","./src/components/common/layout/stdDivider/tests/dividerClassBuilder.test.ts","./src/components/common/layout/stdDropdown/StdDropdown.tsx","./src/components/common/layout/stdDropdown/StdDropdownWithHeader.tsx","./src/components/common/layout/stdDropdown/dropdownClassBuilder.ts","./src/components/common/layout/stdDropdown/subComponents/StdDropdownAddFooter.tsx","./src/components/common/layout/stdDropdown/subComponents/StdDropdownHeader.tsx","./src/components/common/layout/stdDropdown/subComponents/StdDropdownItem.tsx","./src/components/common/layout/stdDropdown/subComponents/StdDropdownSearchHeader.tsx","./src/components/common/layout/stdDropdown/subComponents/StdEmptySearch.tsx","./src/components/common/layout/stdDropdown/test/StdDropdown.test.tsx","./src/components/common/layout/stdDropdown/test/StdDropdownItem.test.tsx","./src/components/common/layout/stdDropdown/test/dropdownClassBuilder.test.ts","./src/components/common/layout/stdFloatingWrapper/StdFloatingWrapper.tsx","./src/components/common/layout/stdFloatingWrapper/slots/stdFloatingElement/StdFloatingElement.tsx","./src/components/common/layout/stdFloatingWrapper/slots/stdFloatingTrigger/StdFloatingTrigger.tsx","./src/components/common/layout/stdFloatingWrapper/tests/stdFloatingWrapper.test.tsx","./src/components/common/layout/stdHeading/StdHeading.tsx","./src/components/common/layout/stdHeading/headingClassBuilder.ts","./src/components/common/layout/stdHeading/tests/StdHeading.test.tsx","./src/components/common/layout/stdHeading/tests/headingClassBuilder.test.ts","./src/components/common/layout/stdModal/StdModal.tsx","./src/components/common/layout/stdModal/StdModalOpener.tsx","./src/components/common/layout/stdModal/modalClassBuilder.ts","./src/components/common/layout/stdModal/slots/StdModalTitle/StdModalTitle.tsx","./src/components/common/layout/stdModal/slots/StdModalTitle/modalTitleClassBuilder.ts","./src/components/common/layout/stdModal/slots/stdModalContents/StdModalContent.tsx","./src/components/common/layout/stdModal/slots/stdModalContents/StdModalLateralContent.tsx","./src/components/common/layout/stdModal/slots/stdModalFooter/StdModalFooter.tsx","./src/components/common/layout/stdModal/slots/stdModalFooter/modalFooterClassBuilder.ts","./src/components/common/layout/stdModalWrapper/StdModalWrapper.tsx","./src/components/common/layout/stdModalWrapper/modalClassBuilder.ts","./src/components/common/layout/stdModalWrapper/tests/modalWrapperClassBuilder.test.ts","./src/components/common/layout/stdModalWrapper/tests/stdModalWrapper.test.tsx","./src/components/common/layout/stdNavbar/StdNavbar.tsx","./src/components/common/layout/stdNavbar/StdNavbarController.tsx","./src/components/common/layout/stdNavbar/StdNavbarHeader.tsx","./src/components/common/layout/stdNavbar/StdNavbarMenu.tsx","./src/components/common/layout/stdNavbar/StdNavbarMenuItem.tsx","./src/components/common/layout/stdNavbar/navbarClassBuilder.ts","./src/components/common/layout/stdNavbar/tests/StdNavbar.test.tsx","./src/components/common/layout/stdNavbar/tests/StdNavbarController.test.tsx","./src/components/common/layout/stdNavbar/tests/StdNavbarHeader.test.tsx","./src/components/common/layout/stdNavbar/tests/StdNavbarMenuItem.test.tsx","./src/components/common/layout/stdNavbar/tests/navbarClassBuilder.test.ts","./src/components/common/layout/stdPopover/StdPopover.tsx","./src/components/common/layout/stdPopover/StdPopoverContent.tsx","./src/components/common/layout/stdPopover/StdPopoverFooter.tsx","./src/components/common/layout/stdPopover/StdPopoverTrigger.tsx","./src/components/common/layout/stdPopover/tests/stdPopover.test.tsx","./src/components/common/layout/stdTextTooltip/StdTextTooltip.tsx","./src/components/common/layout/stdTextTooltip/tests/stdTooltipText.test.tsx","./src/components/common/layout/stdTextWithTooltip/StdTextWithTooltip.tsx","./src/components/common/layout/stdToast/StdToast.tsx","./src/components/common/layout/stdToast/toastClassBuilder.ts","./src/components/common/layout/stdToast/tests/stdToast.test.tsx","./src/components/common/layout/stdToast/tests/toastClassBuilder.test.ts","./src/components/common/layout/stdTooltip/StdTooltip.tsx","./src/components/common/layout/stdTooltip/tests/StdTooltip.test.tsx","./src/components/pegase/header/Header.tsx","./src/components/pegase/navbar/Navbar.tsx","./src/components/pegase/pegaseCard/cardClassBuilder.ts","./src/components/pegase/pegaseCard/pegaseCard.tsx","./src/components/pegase/pegaseCard/useDropdownOptions.ts","./src/components/pegase/pegaseCard/pegaseCardTitle/cardTitleClassBuilder.tsx","./src/components/pegase/pegaseCard/pegaseCardTitle/pegaseCardTitle.tsx","./src/components/pegase/pegaseCard/pegaseCardTitle/tests/cardTitleClassBuilder.test.ts","./src/components/pegase/pegaseCard/pegaseCardTitle/tests/pegaseCardTitle.test.tsx","./src/components/pegase/pegaseCard/tests/cardTripleActionClassBuilder.test.ts","./src/components/pegase/pegaseCard/tests/pegaseCardTripleAction.test.tsx","./src/components/pegase/star/PegaseStar.tsx","./src/contexts/UserContext.tsx","./src/contexts/createFastContext.tsx","./src/contexts/modalContext.ts","./src/hooks/useDateFormatter.ts","./src/hooks/useFocusTrapping.ts","./src/hooks/useInputFormState.ts","./src/hooks/useModal.ts","./src/hooks/useProjectNavigation.ts","./src/hooks/useStdId.ts","./src/hooks/common/useActiveKeyboard.ts","./src/hooks/common/useCallOnResize.ts","./src/hooks/common/useDebounce.ts","./src/hooks/common/useInputFormState.ts","./src/hooks/common/usePrevious.ts","./src/hooks/common/useStdId.ts","./src/hooks/common/useTimeout.ts","./src/hooks/common/test/useCallOnResize.test.ts","./src/hooks/common/test/useDebounce.test.ts","./src/hooks/common/test/useInputFormState.test.ts","./src/hooks/common/test/usePrevious.test.ts","./src/hooks/common/test/useStdId.test.ts","./src/hooks/common/test/useTimeout.test.ts","./src/mocks/mockTools.ts","./src/mocks/data/components/dropdownItems.mock.ts","./src/mocks/data/components/navbarHeader.ts","./src/mocks/data/features/menuItemData.mock.tsx","./src/mocks/data/list/keywords.ts","./src/mocks/data/list/names.ts","./src/mocks/data/list/projectName.ts","./src/mocks/data/list/studyName.ts","./src/mocks/data/list/user.mocks.ts","./src/mocks/data/list/user.ts","./src/pages/pegase/antares/Antares.tsx","./src/pages/pegase/home/HomePage.tsx","./src/pages/pegase/home/components/HomePageContent.tsx","./src/pages/pegase/home/components/SearchBar.tsx","./src/pages/pegase/home/components/StudiesPagination.tsx","./src/pages/pegase/home/components/StudyTableDisplay.tsx","./src/pages/pegase/home/components/StudyTableHeaders.tsx","./src/pages/pegase/home/components/StudyTableUtils.tsx","./src/pages/pegase/home/components/studyService.ts","./src/pages/pegase/home/components/useStudyTableDisplay.ts","./src/pages/pegase/home/components/tests/useStudyTableDisplay.test.ts","./src/pages/pegase/home/pinnedProjects/PinnedProject.tsx","./src/pages/pegase/home/pinnedProjects/PinnedProjectCard.tsx","./src/pages/pegase/home/pinnedProjects/ProjectCreator.tsx","./src/pages/pegase/home/pinnedProjects/tests/handleUnpin.test.ts","./src/pages/pegase/logout/Logout.tsx","./src/pages/pegase/projects/ProjectContent.tsx","./src/pages/pegase/projects/Projects.tsx","./src/pages/pegase/projects/ProjectsPagination.tsx","./src/pages/pegase/projects/projectService.test.tsx","./src/pages/pegase/projects/projectService.ts","./src/pages/pegase/projects/projectDetails/ProjectDetails.tsx","./src/pages/pegase/projects/projectDetails/ProjectDetailsContent.tsx","./src/pages/pegase/projects/projectDetails/ProjectDetailsHeader.tsx","./src/pages/pegase/projects/projectDetails/fetchProjectDetails.test.tsx","./src/pages/pegase/reports/LogsPage.tsx","./src/pages/pegase/settings/Settings.tsx","./src/pages/pegase/studies/HorizonInput.tsx","./src/pages/pegase/studies/KeywordsInput.tsx","./src/pages/pegase/studies/ProjectInput.tsx","./src/pages/pegase/studies/StudyCreationModal.tsx","./src/shared/constants.ts","./src/shared/notification/containers.tsx","./src/shared/notification/notification.tsx","./src/shared/types/index.ts","./src/shared/types/common/DisplayStatus.type.ts","./src/shared/types/common/MenuNavItem.type.ts","./src/shared/types/common/StdBase.type.ts","./src/shared/types/common/StudyStatus.type.ts","./src/shared/types/common/Tailwind.type.ts","./src/shared/types/common/TailwindColorClass.type.ts","./src/shared/types/common/User.type.ts","./src/shared/types/common/UserSettings.type.ts","./src/shared/types/common/tests/testUtils.tsx","./src/shared/types/pegase/Project.type.ts","./src/shared/types/pegase/study.ts","./src/shared/utils/dateFormatter.ts","./src/shared/utils/slotsUtils.ts","./src/shared/utils/tabIndexUtils.ts","./src/shared/utils/common/defaultUtils.ts","./src/shared/utils/common/displayUtils.ts","./src/shared/utils/common/slotsUtils.ts","./src/shared/utils/common/classes/classMerger.ts","./src/shared/utils/common/classes/test/classMerger.test.ts","./src/shared/utils/common/dom/getDimensions.ts","./src/shared/utils/common/dom/test/getDimensions.test.tsx","./src/shared/utils/common/mappings/iconMaps.ts","./tailwind.config.ts","./vite-env.d.ts"],"version":"5.7.2"} \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index e7a3899..ef361c0 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,10 +7,21 @@ import react from '@vitejs/plugin-react'; import path from 'path'; import { defineConfig } from 'vite'; +import topLevelAwait from 'vite-plugin-top-level-await'; var DEFAULT_PORT = 8080; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + topLevelAwait({ + // The export name of top-level await promise for each chunk module + promiseExportName: '__tla', + // The function to generate import names of top-level await promise in each chunk module + promiseImportName: function (i) { + return '__tla_'.concat(i); + }, + }), + ], build: { target: 'esnext', rollupOptions: { diff --git a/vite.config.ts b/vite.config.ts index cf4e75d..4f95819 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,17 +3,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - /// import react from '@vitejs/plugin-react'; import path from 'path'; import { defineConfig } from 'vite'; +import topLevelAwait from 'vite-plugin-top-level-await'; const DEFAULT_PORT = 8080; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + topLevelAwait({ + // The export name of top-level await promise for each chunk module + promiseExportName: '__tla', + // The function to generate import names of top-level await promise in each chunk module + promiseImportName: (i) => `__tla_${i}`, + }), + ], build: { target: 'esnext', rollupOptions: {