Skip to content

Commit

Permalink
feat: add expandable submenu to navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
SannyNguyenHung committed Jan 29, 2024
1 parent 41ebe36 commit 0e1ade1
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 62 deletions.
142 changes: 116 additions & 26 deletions app/components/FlowNavigation.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -29,19 +34,30 @@ export default function FlowNavigation({
}) {
return (
<ul>
{navItems.map(({ destination, label, state, subflows }) =>
navItem(destination, state, label, subflows),
)}
{navItems.map(({ destination, label, state, subflows }) => (
<NavItem
key={destination}
destination={destination}
state={state}
label={label}
subflows={subflows ?? []}
/>
))}
</ul>
);
}

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;
Expand All @@ -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,
);
Expand All @@ -78,9 +99,9 @@ function navItem(
key={destination}
className={"list-none border-t-2 border-white first:border-t-0"}
>
<a
href={destination}
className={`p-16 flex gap-x-16 items-center
{hasActiveSubflows ? (
<div
className={`p-16 pr-0 flex gap-x-16 items-center min-w-[242px]
${
state === NavState.Current
? "ds-label-02-bold"

Check warning on line 107 in app/components/FlowNavigation.tsx

View workflow job for this annotation

GitHub Actions / code-quality / npm run eslint:check

Define a constant instead of duplicating this literal 3 times
Expand All @@ -91,29 +112,98 @@ function navItem(
? "text-gray-600 curser-not-allowed hover:font-normal pointer-events-none"

Check warning on line 112 in app/components/FlowNavigation.tsx

View workflow job for this annotation

GitHub Actions / code-quality / npm run eslint:check

Define a constant instead of duplicating this literal 3 times
: ""
}
${hasActiveSubflows && !isDisabled ? "border-b-2 border-white" : ""}
${!isDisabled && isExpanded ? "border-b-2 border-white" : ""}
`}
aria-disabled={[NavState.DoneDisabled, NavState.OpenDisabled].includes(
state,
)}
>
<StateIcon state={state} />
{label}
</a>
aria-disabled={[
NavState.DoneDisabled,
NavState.OpenDisabled,
].includes(state)}
>
<button
className="relative flex items-center w-full cursor-pointer flex gap-x-16 items-center"
{...getToggleProps()}
>
<StateIcon state={state} />
{label}
<i className="absolute right-0 pt-1 text-base transition-transform fa fa-chevron-down group-open:rotate-180"></i>
{isExpanded ? (
<ExpandLessIcon className="ml-auto" />
) : (
<ExpandMoreIcon className="ml-auto" />
)}
</button>
</div>
) : (
getNavRootItem(destination, state, isDisabled, hasActiveSubflows, label)
)}
{hasActiveSubflows && (
<ul className="pt-8 pl-32 mr-8 pl-0 min-w-fit max-w-fit md:min-w-[250px] md:max-w-[250px] break-words">
{relevantSubflows.map(({ destination, label, state }) =>
navSubflowItem(destination, state, label),
)}
</ul>
<section className="w-[245px]" {...getCollapseProps()}>
<SubflowNavigation
subflows={relevantSubflows}
destination={destination}
/>
</section>
)}
</li>
);
}

function SubflowNavigation({
subflows,
destination,
}: {
readonly subflows: NavItem[];
readonly destination: string;
}) {
return (
<ul className="pt-8 pl-32 mr-8 pl-0 min-w-fit max-w-fit md:min-w-[250px] md:max-w-[250px] break-words">
{subflows.map(({ destination, label, state }) =>
navSubflowItem(destination, state, label),
)}
</ul>
);
}

function getNavRootItem(
destination: string,
state: NavState,
isDisabled: boolean,
hasActiveSubflows: boolean,
label: string,
) {
return (
<a
href={destination}
className={`p-16 flex gap-x-16 items-center
${
state === NavState.Current
? "ds-label-02-bold"
: "ds-label-02-reg hover:font-bold"
}
${
isDisabled
? "text-gray-600 curser-not-allowed hover:font-normal pointer-events-none"
: ""
}
${hasActiveSubflows && !isDisabled ? "border-b-2 border-white" : ""}
`}
aria-disabled={[NavState.DoneDisabled, NavState.OpenDisabled].includes(
state,
)}
>
<StateIcon state={state} />
{label}
</a>
);
}

function navSubflowItem(destination: string, state: NavState, label: string) {
return (
<li key={destination} className="list-none">
<li
data-collapse={`collapse-${destination}`}
key={destination}
className="list-none"
>
<a
href={destination}
className={`p-16 flex gap-x-16 items-center
Expand Down
58 changes: 29 additions & 29 deletions app/services/flowNavigation.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export function navItemsFromFlowSpecifics(
const currentFlow = flowController.getConfig();
const flowRoot = currentFlow.id ?? "";

// Fixme Sanny: This function is too complex
// eslint-disable-next-line sonarjs/cognitive-complexity
return getSubflowsEntries(currentFlow).map(([rootStateName, flowEntry]) => {
const subflows =
Expand All @@ -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,
Expand All @@ -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,
};
});
}
Expand Down
37 changes: 30 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 0e1ade1

Please sign in to comment.