114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js";
|
|
import { ChevronDown } from "../../../lib/icons";
|
|
import { activeDepartment, departmentItems, type DepartmentItem } from "../data/shell.data";
|
|
import styles from "./DepartmentSelector.module.scss";
|
|
|
|
const defaultDepartment = departmentItems.find((item) => item.id === activeDepartment.id) ?? departmentItems[0];
|
|
const defaultTeamName = departmentItems
|
|
.find((item) => item.id === activeDepartment.id)
|
|
?.teams.find((teamName) => teamName === activeDepartment.teamName)
|
|
?? defaultDepartment?.teams[0]
|
|
?? "";
|
|
|
|
export const DepartmentSelector = (): JSX.Element => {
|
|
const [isOpen, setIsOpen] = createSignal(false);
|
|
const [selectedDepartment, setSelectedDepartment] = createSignal<DepartmentItem>(defaultDepartment);
|
|
const [selectedTeamName, setSelectedTeamName] = createSignal(defaultTeamName);
|
|
|
|
let rootRef: HTMLDivElement | undefined;
|
|
|
|
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={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>
|
|
);
|
|
};
|