diff --git a/app/components/FlowNavigation.tsx b/app/components/FlowNavigation.tsx
index 0fa8a547f..3792f9cd4 100644
--- a/app/components/FlowNavigation.tsx
+++ b/app/components/FlowNavigation.tsx
@@ -1,5 +1,10 @@
import CheckCircleOutlineIcon from "@digitalservicebund/icons/CheckCircleOutline";
import CircleOutlinedIcon from "@digitalservicebund/icons/CircleOutlined";
+import React from "react";
+import ExpandLessIcon from "@digitalservicebund/icons/ExpandLess";
+import ExpandMoreIcon from "@digitalservicebund/icons/ExpandMore";
+
+import { useCollapse } from "react-collapsed";
export enum NavState {
DoneDisabled,
@@ -29,19 +34,30 @@ export default function FlowNavigation({
}) {
return (
- {navItems.map(({ destination, label, state, subflows }) =>
- navItem(destination, state, label, subflows),
- )}
+ {navItems.map(({ destination, label, state, subflows }) => (
+
+ ))}
);
}
-function navItem(
- destination: string,
- state: NavState,
- label: string,
- subflows = [] as NavItem[],
-) {
+function NavItem({
+ destination,
+ label,
+ state,
+ subflows,
+}: {
+ readonly destination: string;
+ readonly state: NavState;
+ readonly label: string;
+ readonly subflows: NavItem[];
+}) {
const relevantSubflows = subflows.filter((subflow, index) => {
const isCurrentState = subflow.state === NavState.Current;
const isDoneState = subflow.state === NavState.Done;
@@ -62,6 +78,11 @@ function navItem(
);
});
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const { getCollapseProps, getToggleProps, isExpanded } = useCollapse({
+ defaultExpanded: state === NavState.Current,
+ });
+
const isDisabled = [NavState.DoneDisabled, NavState.OpenDisabled].includes(
state,
);
@@ -78,9 +99,9 @@ function navItem(
key={destination}
className={"list-none border-t-2 border-white first:border-t-0"}
>
-
-
- {label}
-
+ aria-disabled={[
+ NavState.DoneDisabled,
+ NavState.OpenDisabled,
+ ].includes(state)}
+ >
+
+
+ ) : (
+ getNavRootItem(destination, state, isDisabled, hasActiveSubflows, label)
+ )}
{hasActiveSubflows && (
-
- {relevantSubflows.map(({ destination, label, state }) =>
- navSubflowItem(destination, state, label),
- )}
-
+
)}
);
}
+function SubflowNavigation({
+ subflows,
+ destination,
+}: {
+ readonly subflows: NavItem[];
+ readonly destination: string;
+}) {
+ return (
+
+ {subflows.map(({ destination, label, state }) =>
+ navSubflowItem(destination, state, label),
+ )}
+
+ );
+}
+
+function getNavRootItem(
+ destination: string,
+ state: NavState,
+ isDisabled: boolean,
+ hasActiveSubflows: boolean,
+ label: string,
+) {
+ return (
+
+
+ {label}
+
+ );
+}
+
function navSubflowItem(destination: string, state: NavState, label: string) {
return (
-
+
{
const subflows =
@@ -37,6 +36,34 @@ export function navItemsFromFlowSpecifics(
}`;
const rootLabel = translation[rootStateName] ?? flowEntry.key ?? "No key";
+ const subflowSpecifics =
+ subflows.length > 0
+ ? _.flatten(
+ subflows.map((entry) => {
+ const subflow = entry[1] ?? {};
+ const subflowKey = entry[0] ?? "No key";
+ const subflowRoot = `${rootStateName}/${subflow?.id ?? ""}`;
+ const subflowDestionationStepId = `${subflowRoot}/${
+ typeof subflow?.initial === "string" ? subflow?.initial : ""
+ }`;
+ const subflowLabel = translation[subflowRoot] ?? subflowKey;
+
+ return {
+ destination: flowRoot + subflowDestionationStepId,
+ label: subflowLabel ?? subflowKey,
+ state: navState({
+ isCurrent: currentStepId.startsWith(subflowRoot),
+ isReachable: true,
+ isDone: flowController.isSubflowDone(
+ rootStateName,
+ subflowKey,
+ ),
+ isUneditable: flowController.isUneditable(subflowRoot),
+ }),
+ };
+ }),
+ )
+ : [];
return {
destination: flowRoot + destinationStepId,
@@ -47,34 +74,7 @@ export function navItemsFromFlowSpecifics(
isDone: flowController.isDone(rootStateName),
isUneditable: flowController.isUneditable(rootStateName),
}),
- subflows:
- subflows.length > 0
- ? _.flatten(
- subflows.map((entry) => {
- const subflow = entry[1] ?? {};
- const subflowKey = entry[0] ?? "No key";
- const subflowRoot = `${rootStateName}/${subflow?.id ?? ""}`;
- const subflowDestionationStepId = `${subflowRoot}/${
- typeof subflow?.initial === "string" ? subflow?.initial : ""
- }`;
- const subflowLabel = translation[subflowRoot] ?? subflowKey;
-
- return {
- destination: flowRoot + subflowDestionationStepId,
- label: subflowLabel ?? subflowKey,
- state: navState({
- isCurrent: currentStepId.startsWith(subflowRoot),
- isReachable: true,
- isDone: flowController.isSubflowDone(
- rootStateName,
- subflowKey,
- ),
- isUneditable: flowController.isUneditable(subflowRoot),
- }),
- };
- }),
- )
- : [],
+ subflows: subflowSpecifics,
};
});
}
diff --git a/package-lock.json b/package-lock.json
index 7ad0e7ba1..7f056b848 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"@remix-run/serve": "^2.5.1",
"@remix-validated-form/with-zod": "^2.0.7",
"@sentry/remix": "^7.98.0",
+ "@types/react-collapse": "^5.0.4",
"@xstate/graph": "^1.4.2",
"axios": "^1.6.7",
"classnames": "^2.5.1",
@@ -32,6 +33,7 @@
"posthog-node": "^3.6.0",
"quicktype-core": "^23.0.81",
"react": "^18.2.0",
+ "react-collapsed": "^4.1.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-imask": "^7.3.0",
@@ -9264,8 +9266,7 @@
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
- "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
- "devOptional": true
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
},
"node_modules/@types/qs": {
"version": "6.9.11",
@@ -9283,13 +9284,20 @@
"version": "18.2.48",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
"integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
- "devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-collapse": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-collapse/-/react-collapse-5.0.4.tgz",
+ "integrity": "sha512-tM5cVB6skGLneNYnRK2E3R56VOHguSeJQHslGPTIMC58ytL3oelT8L/l1onkwHGn5vSEs2BEq2Olzrur+YdliA==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-dom": {
"version": "18.2.18",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz",
@@ -9308,8 +9316,7 @@
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
- "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
- "devOptional": true
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
},
"node_modules/@types/semver": {
"version": "7.5.6",
@@ -12455,8 +12462,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -23478,6 +23484,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-collapsed": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/react-collapsed/-/react-collapsed-4.1.2.tgz",
+ "integrity": "sha512-vusqVnW/VbFCKJx29KNTnjJrwCbV7k3bB/FiO9/0Fj7JNoNarkU1xU7yK4FZHqPW0Q2izB7o6fYzG16zgdDNMQ==",
+ "dependencies": {
+ "tiny-warning": "^1.0.3"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17 || ^18",
+ "react-dom": "^16.9.0 || ^17 || ^18"
+ }
+ },
"node_modules/react-colorful": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
@@ -26165,6 +26183,11 @@
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
},
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
diff --git a/package.json b/package.json
index 415a49215..67a6d954f 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
"posthog-node": "^3.6.0",
"quicktype-core": "^23.0.81",
"react": "^18.2.0",
+ "react-collapsed": "^4.1.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-imask": "^7.3.0",