diff --git a/package.json b/package.json index 00b52144..01678863 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inseefr/lunatic-dsfr", - "version": "0.0.26", + "version": "0.0.27", "description": "Couche graphique pour Lunatic reposant sur le Système de Design de l'État (DSFR)", "repository": { "type": "git", diff --git a/src/Button.tsx b/src/Button.tsx index 0a8d4b29..e4fc9c3a 100644 --- a/src/Button.tsx +++ b/src/Button.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { PropsWithChildren, useCallback } from "react"; import classnames from "classnames"; import { isElement } from "./utils/is-element"; import { Button as ButtonDSFR } from "@codegouvfr/react-dsfr/Button"; @@ -10,13 +10,12 @@ export function Button({ className, priority, onClick, -}: { - children: string | React.ReactNode; +}: PropsWithChildren<{ disabled: boolean; - className: string; + className?: string; priority: ButtonProps.Common["priority"]; onClick: (e: React.MouseEvent) => void; -}) { +}>) { const handleClick = useCallback( function (e: React.MouseEvent) { e.stopPropagation(); diff --git a/src/Modal.tsx b/src/Modal.tsx new file mode 100644 index 00000000..a7925499 --- /dev/null +++ b/src/Modal.tsx @@ -0,0 +1,114 @@ +import { useCallback, useEffect, useRef, KeyboardEvent } from "react"; +import { createPortal } from "react-dom"; +import { Button } from "./Button"; +import { LunaticComponentProps } from "./type"; +import classnames from "classnames"; + +export function Modal( + props: Pick< + LunaticComponentProps<"Modal">, + "id" | "label" | "description" | "goToPage" | "page" | "goNextPage" | "goPreviousPage" + >, +) { + const { id, label, description, goNextPage, goPreviousPage } = props; + const first = useRef(null); + const last = useRef(null); + + useEffect(() => { + const focusOnInit = first?.current?.lastElementChild as HTMLButtonElement; + focusOnInit.focus(); + }, [first]); + + const onKeyDown = useCallback( + (e: KeyboardEvent) => { + const firstButtonToFocus = first?.current?.lastElementChild as HTMLButtonElement; + const lastButtonToFocus = last?.current?.lastElementChild as HTMLButtonElement; + if (e.key === "Tab") { + if (e.shiftKey) { + if (document.activeElement === firstButtonToFocus) { + lastButtonToFocus.focus(); + e.nativeEvent.preventDefault(); + } + } else if (document.activeElement === lastButtonToFocus) { + firstButtonToFocus.focus(); + e.nativeEvent.preventDefault(); + } + } + }, + [first, last], + ); + + const handleNextClick = useCallback( + function () { + goNextPage(); + }, + [goNextPage], + ); + const handlePreviousClick = useCallback( + function () { + goPreviousPage(); + }, + [goPreviousPage], + ); + + return createPortal( + +
+
+
+
+
+ +
+
+ + {/* Label is defined in the source.json, and we can accept VTL|MD or VTL */} + {label} + + {/* Label is defined in the source.json, and we can accept VTL|MD or VTL */} + {description} +
+
+
    +
  • + +
  • +
  • + +
  • +
+
+
+
+
+
+
, + document.body, + ); +} + +export default Modal; diff --git a/src/index.ts b/src/index.ts index af2dfd9c..1d839c83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,3 +13,4 @@ export * from "./Sequence"; export * from "./Subsequence"; export * from "./Summary"; export * from "./QuestionExplication"; +export * from "./Modal"; diff --git a/src/stories/modal/modal.stories.jsx b/src/stories/modal/modal.stories.jsx new file mode 100644 index 00000000..3dbe4363 --- /dev/null +++ b/src/stories/modal/modal.stories.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import defaultArgTypes from "../utils/default-arg-types"; +import Orchestrator from "../utils/Orchestrator"; +import source from "./source.json"; +import * as custom from "../.."; + +// eslint-disable-next-line no-undef +console.log(custom); + +const stories = { + title: "Components/Modal", + component: Orchestrator, + argTypes: defaultArgTypes, +}; + +export default stories; + +const Template = args => ; +export const Default = Template.bind({}); + +Default.args = { id: "modal", source }; diff --git a/src/stories/modal/source.json b/src/stories/modal/source.json new file mode 100644 index 00000000..66a9ef2e --- /dev/null +++ b/src/stories/modal/source.json @@ -0,0 +1,97 @@ +{ + "maxPage": "4", + "components": [ + { + "id": "kze792d8", + "componentType": "InputNumber", + "mandatory": false, + "page": "1", + "min": 0, + "max": 10, + "decimals": 0, + "label": { + "value": "\"➡ 1. \" || \"In put for Number \"", + "type": "VTL|MD" + }, + "description": { "value": "\"Description\"", "type": "VTL|MD" }, + "conditionFilter": { "value": "true", "type": "VTL" }, + "response": { "name": "COUNT_PERSONS" } + }, + { + "id": "modaltoto", + "componentType": "ConfirmationModal", + "conditionFilter": { "value": "true", "type": "VTL" }, + "page": "2", + "label": { + "value": "\"Vous avez indiqué \" || (if COUNT_PERSONS = 0 then \"que personne vivait\" else if COUNT_PERSONS = 1 then \"qu'une personne vivait\" else \"que \" || cast(COUNT_PERSONS, string) || \" personnes vivait\") || \" dans ce logement. Êtes-vous sur de n’avoir oublié personne, y compris: \"", + "type": "VTL" + }, + "description": { + "value": "\"* vous-même \n * les nourrissons encore à la maternité \n * les personnes temporairement absentes (vacances, voyage d'affaires, hospitalisation de moins d'un mois, etc.) \n * les personnes qui vivent également une partie du temps ailleurs (enfants ou étudiants scolarisés ailleurs, conjoints éloignés pour raisons professionnelles, enfants en résidence alternée, personnes âgées en institution, etc.) \n * les colocataires et les sous-locataires \n\"", + "type": "VTL|MD" + }, + "declarations": [ + { + "id": "kb9hi4j0-krnoclfe", + "declarationType": "INSTRUCTION", + "position": "BEFORE_QUESTION_TEXT", + "label": { + "value": "\"Déclaration Before\"", + "type": "VTL|MD" + } + }, + { + "id": "kb9hi4j0-krnoclfe", + "declarationType": "INSTRUCTION", + "position": "AFTER_QUESTION_TEXT", + "label": { + "value": "\"Déclaration AFTER\"", + "type": "VTL|MD" + } + }, + { + "id": "kb9hi4j0-krnoclfe", + "declarationType": "HELP", + "position": "DETACHABLE", + "label": { + "value": "\"Declaration Detachable\"", + "type": "VTL|MD" + } + } + ] + }, + { + "id": "modaltoto", + "componentType": "Sequence", + "conditionFilter": { "value": "false", "type": "VTL" }, + "page": "3", + "label": { + "value": "toto to skip", + "type": "VTL" + } + }, + { + "id": "modaltoto", + "componentType": "Sequence", + "conditionFilter": { "value": "true", "type": "VTL" }, + "page": "4", + "label": { + "value": "toto to not skip", + "type": "VTL" + } + } + ], + "variables": [ + { + "variableType": "COLLECTED", + "name": "COUNT_PERSONS", + "values": { + "PREVIOUS": null, + "COLLECTED": null, + "FORCED": null, + "EDITED": null, + "INPUTED": null + } + } + ] +} diff --git a/src/type.ts b/src/type.ts index fcd220a8..ee7877a0 100644 --- a/src/type.ts +++ b/src/type.ts @@ -189,8 +189,8 @@ type ComponentPropsByType = { Modal: LunaticBaseProps & { goToPage: (payload: Record) => void; page: string; - goNextPage?: () => void; - goPreviousPage?: () => void; + goNextPage: () => void; + goPreviousPage: () => void; }; };