"use client";
import { useState } from "react";
import { Check, ChevronDown, Settings, SlidersHorizontal } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { HERO_CONTROL } from "@/components/ui/map-hero";
import { cn } from "@/lib/utils";
import { DiveFormFieldsDialog } from "@/components/dives/dive-form-fields-dialog";
import { useDiveFormPresets } from "@/hooks/useDiveFormPresets";
import {
ALL_FIELDS_PRESET_NAME,
currentDiveFormPreset,
} from "@/lib/api/dive-form-presets";
import type { DiveFormVisibility } from "@/hooks/useDiveFormVisibility";
/** What the trigger reads while the account's presets are still on the wire. */
const LOADING_LABEL = "Fields";
/**
* What a hidden set matching no saved preset is called - on the trigger, and nowhere
* else. It is deliberately not an entry in the menu: there is nothing to apply, and a
* row that cannot be picked in a list of rows that can is a trap.
*/
const CUSTOM_LABEL = "Custom";
interface DiveFormFieldsMenuProps {
visibility: DiveFormVisibility;
}
/**
* The Fields control in the dive form page's top row, opposite the way back: a menu of
* the account's presets, and Configure for everything else. Dressed as the back link
* is, a hero control.
*
* **The trigger is labelled with the state, not with the control's name**, by
* `currentDiveFormPreset`: the preset the diver picked while its set still matches,
* "All" for nothing hidden, and "Custom" for a set no preset holds. Save as seeds its
* name from the same answer, so the two can never disagree. The accessible name keeps
* "Fields" in front of it, so the control is still findable by what it does.
*
* **Picking a preset applies its set and remembers the pick.** A preset is a snapshot:
* applying copies its hidden set into the account's current state, and editing a field
* afterwards changes the state rather than the preset - at which point the trigger
* says "Custom" until the diver writes it back from Configure.
*/
export function DiveFormFieldsMenu({ visibility }: DiveFormFieldsMenuProps) {
const presets = useDiveFormPresets();
const [isConfigureOpen, setIsConfigureOpen] = useState(false);
const rows = presets.presets;
// One answer, shared with Save as: the preset the diver picked while its set still
// matches, so two presets holding the same set are told apart by which was applied.
const current = currentDiveFormPreset(
rows,
visibility.hidden,
visibility.selectedPresetUuid,
);
const label =
current.kind === "saved"
? current.preset.name
: current.kind === "all"
? ALL_FIELDS_PRESET_NAME
: current.kind === "loading"
? LOADING_LABEL
: CUSTOM_LABEL;
const presetItem = (
key: string,
name: string,
isCurrent: boolean,
onSelect: () => void,
) => (
{/* The check is a mark *and* a word, for the reason the dialog's own list
carries: colour and an icon alone would leave the current preset unnamed
to a screen reader. */}
{name}
{isCurrent && (current fields)}
);
return (
<>
}
>
{/* Sliders, then the state, then the chevron: what the control is about,
what it currently says, and last the mark that it opens - which is
where a menu button conventionally carries it. `aria-hidden` on both
icons, since the trigger is named by `aria-label` and Base UI has
already said `haspopup`. */}
{label}
{presetItem(
"all",
ALL_FIELDS_PRESET_NAME,
current.kind === "all",
() => visibility.applyPreset([], null),
)}
{presets.isLoading ? (
Loading presets...
) : (
rows?.map((preset) =>
presetItem(
preset.uuid,
preset.name,
current.kind === "saved" && current.preset.uuid === preset.uuid,
() => visibility.applyPreset(preset.hidden_fields, preset.uuid),
),
)
)}
{/* The dialog opens from a menu item that disappears on selection.
DialogContent returns focus to the Fields button when it closes. */}
setIsConfigureOpen(true)}>
Configure...
>
);
}