Ctrl K
ComunidadGuías…

src/components/dives/dive-form-fields-menu.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"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,
  ) => (
    <DropdownMenuItem key={key} onClick={onSelect}>
      {/* 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. */}
      <Check
        className={`mr-2 h-3.5 w-3.5 text-teal ${isCurrent ? "" : "invisible"}`}
        aria-hidden
      />
      <span aria-current={isCurrent ? "true" : undefined}>{name}</span>
      {isCurrent && <span className="sr-only">(current fields)</span>}
    </DropdownMenuItem>
  );

  return (
    <>
      <DropdownMenu>
        <DropdownMenuTrigger
          // The visible text names the current preset; include the control's
          // purpose while preserving that text in its accessible name.
          aria-label={label === LOADING_LABEL ? undefined : `Fields: ${label}`}
          render={
            <Button
              type="button"
              variant="ghost"
              size="sm"
              className={cn("gap-2", HERO_CONTROL)}
            />
          }
        >
          {/* 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`. */}
          <SlidersHorizontal className="h-4 w-4" aria-hidden />
          {label}
          <ChevronDown className="h-4 w-4" aria-hidden />
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end" className="min-w-[12rem]">
          {presetItem(
            "all",
            ALL_FIELDS_PRESET_NAME,
            current.kind === "all",
            () => visibility.applyPreset([], null),
          )}
          {presets.isLoading ? (
            <DropdownMenuItem disabled>Loading presets...</DropdownMenuItem>
          ) : (
            rows?.map((preset) =>
              presetItem(
                preset.uuid,
                preset.name,
                current.kind === "saved" && current.preset.uuid === preset.uuid,
                () => visibility.applyPreset(preset.hidden_fields, preset.uuid),
              ),
            )
          )}
          <DropdownMenuSeparator />
          {/* The dialog opens from a menu item that disappears on selection.
              DialogContent returns focus to the Fields button when it closes. */}
          <DropdownMenuItem onClick={() => setIsConfigureOpen(true)}>
            <Settings className="mr-2 h-3.5 w-3.5" aria-hidden />
            Configure...
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>

      <DiveFormFieldsDialog
        open={isConfigureOpen}
        onOpenChange={setIsConfigureOpen}
        visibility={visibility}
        presets={presets}
      />
    </>
  );
}