Files
Work/Frontend/src/components/shell/DepartmentSelector/DepartmentSelector.tsx
T
2026-06-19 17:39:39 +01:00

121 lines
4.1 KiB
TypeScript

import { For, createEffect, createSignal, onCleanup, onMount, type JSX } from "solid-js";
import { ChevronDown } from "../../../lib/icons";
import { useAppShellData } from "../data/app-shell.context";
import { type DepartmentItem } from "../data/shell.data";
import styles from "./DepartmentSelector.module.scss";
export const DepartmentSelector = (): JSX.Element => {
const appShellData = useAppShellData();
const [isOpen, setIsOpen] = createSignal(false);
const [selectedDepartment, setSelectedDepartment] = createSignal<DepartmentItem>(appShellData.departmentItems()[0] ?? appShellData.activeDepartment());
const [selectedTeamName, setSelectedTeamName] = createSignal(appShellData.activeDepartment().teamName);
let rootRef: HTMLDivElement | undefined;
createEffect(() => {
const activeDepartment = appShellData.activeDepartment();
const matchingDepartment = appShellData.departmentItems().find((item) => item.id === activeDepartment.id) ?? appShellData.departmentItems()[0];
if (!matchingDepartment) {
return;
}
setSelectedDepartment(matchingDepartment);
setSelectedTeamName(activeDepartment.teamName || matchingDepartment.teams[0] || "");
});
onMount(() => {
const handlePointerDown = (event: PointerEvent): void => {
if (!isOpen()) return;
if (!rootRef) return;
const target = event.target;
if (target instanceof Node && !rootRef.contains(target)) {
setIsOpen(false);
}
};
document.addEventListener("pointerdown", handlePointerDown);
onCleanup(() => document.removeEventListener("pointerdown", handlePointerDown));
});
const selectDepartment = (item: DepartmentItem): void => {
setSelectedDepartment(item);
setSelectedTeamName(item.teams[0] ?? "");
};
const selectTeam = (teamName: string): void => {
setSelectedTeamName(teamName);
setIsOpen(false);
};
return (
<div class={styles.root} ref={rootRef}>
<button
classList={{ [styles.selector]: true, [styles.selectorOpen]: isOpen() }}
type="button"
aria-label="Select department"
title="Select department"
aria-haspopup="menu"
aria-expanded={isOpen()}
onClick={() => setIsOpen((open) => !open)}
>
<span class={styles.copy}>
<strong class={styles.value}>{selectedDepartment().name}</strong>
<span class={styles.meta}>{selectedTeamName()}</span>
</span>
<ChevronDown classList={{ [styles.icon]: true, [styles.iconOpen]: isOpen() }} size={16} strokeWidth={2} />
</button>
{isOpen() ? (
<div class={styles.menu} role="menu" aria-label="Department selector menu">
<div class={styles.menuSection}>
<span class={styles.menuSectionLabel}>Departments</span>
<For each={appShellData.departmentItems()}>
{(item): JSX.Element => (
<button
classList={{ [styles.menuItem]: true, [styles.menuItemActive]: item.id === selectedDepartment().id }}
type="button"
role="menuitemradio"
aria-checked={item.id === selectedDepartment().id}
onClick={() => selectDepartment(item)}
>
<div class={styles.menuItemCopy}>
<strong class={styles.menuItemValue}>{item.name}</strong>
<span class={styles.menuItemMeta}>{item.teams.length} teams</span>
</div>
</button>
)}
</For>
</div>
<div class={styles.menuDivider} aria-hidden="true" />
<div class={styles.menuSection}>
<span class={styles.menuSectionLabel}>Teams in {selectedDepartment().name}</span>
<For each={selectedDepartment().teams}>
{(teamName): JSX.Element => (
<button
classList={{ [styles.submenuItem]: true, [styles.submenuItemActive]: teamName === selectedTeamName() }}
type="button"
role="menuitemradio"
aria-checked={teamName === selectedTeamName()}
onClick={() => selectTeam(teamName)}
>
<span class={styles.submenuIndicator} aria-hidden="true" />
<div class={styles.menuItemCopy}>
<strong class={styles.menuItemValue}>{teamName}</strong>
</div>
</button>
)}
</For>
</div>
</div>
) : null}
</div>
);
};