Ctrl K
ComunidadGuías…

src/components/contacts/contact-position-field.tsx

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

Archivo original ↗Preguntar a KiBot
"use client";

import { useState } from "react";
import { useWatch, type UseFormReturn } from "react-hook-form";
import { MapPin } from "lucide-react";
import { parseCoordinatePair } from "@/lib/validations/coordinates";
import type { ContactInput } from "@/lib/validations/contact";
import { MapField } from "@/components/map/map-field";
import {
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

const COORDINATE_HINT =
  "Paste a “27.8506, 34.3136” pair into either field to fill both, or place the contact on the map.";

const COORDINATES = [
  { name: "latitude", label: "Latitude", placeholder: "e.g. 27.8506" },
  { name: "longitude", label: "Longitude", placeholder: "e.g. 34.3136" },
] as const;

interface ContactPositionFieldProps {
  form: UseFormReturn<ContactInput>;
}

/**
 * Where a contact is: the latitude and longitude inputs, and the map the dive
 * site form shows, drawn only once the diver asks for it.
 *
 * Not on open, because a map on screen has the browser fetch tiles from the
 * basemap host, and this dialog opens from the dive, course, certification,
 * service-record and trip forms as well - a diver adding a dive center from a
 * dive would contact that host without having asked for a map. `/privacy` §4.4
 * says the same. The state needs no reset: the dialog's content unmounts when
 * it closes, so every opening starts with the map closed.
 *
 * No geocoding: the address is not looked up into a pin, nor a pin into an
 * address, so this field reaches no host until the map is drawn.
 */
export function ContactPositionField({ form }: ContactPositionFieldProps) {
  const [mapOpen, setMapOpen] = useState(false);
  const [latitude, longitude] = useWatch({
    control: form.control,
    name: ["latitude", "longitude"],
  });

  // The map and the paste write into the two inputs rather than holding a
  // position of their own. `shouldValidate` because a whole pair clears the
  // both-or-neither message.
  const setPosition = (position: { latitude: string; longitude: string }) => {
    const options = { shouldValidate: true, shouldDirty: true };
    form.setValue("latitude", position.latitude, options);
    form.setValue("longitude", position.longitude, options);
  };

  const handleCoordinatePaste = (
    event: React.ClipboardEvent<HTMLInputElement>,
  ) => {
    const pair = parseCoordinatePair(event.clipboardData.getData("text"));
    if (!pair) return;
    event.preventDefault();
    setPosition(pair);
  };

  return (
    <div className="space-y-4">
      {/* No `inputMode="decimal"`, for the dive site form's reason: iOS's keypad
          for it has no minus key. */}
      <div className="grid gap-4 sm:grid-cols-2">
        {COORDINATES.map((coordinate) => (
          <FormField
            key={coordinate.name}
            control={form.control}
            name={coordinate.name}
            render={({ field }) => (
              <FormItem>
                <FormLabel>{coordinate.label}</FormLabel>
                <FormControl
                  render={
                    <Input
                      autoComplete="off"
                      placeholder={coordinate.placeholder}
                      onPaste={handleCoordinatePaste}
                      {...field}
                    />
                  }
                />
                {/* Announced with each input and shown once below the pair -
                    the dive site form's arrangement, for its reason. */}
                <FormDescription className="sr-only">
                  {COORDINATE_HINT}
                </FormDescription>
                <FormMessage />
              </FormItem>
            )}
          />
        ))}
      </div>

      <p className="-mt-2 text-sm text-muted-foreground" aria-hidden>
        {COORDINATE_HINT}
      </p>

      {mapOpen ? (
        <MapField
          subject="the contact"
          latitude={latitude}
          longitude={longitude}
          onPick={setPosition}
        />
      ) : (
        <Button
          type="button"
          variant="outline"
          onClick={() => setMapOpen(true)}
        >
          <MapPin className="h-4 w-4" />
          Show map
        </Button>
      )}
    </div>
  );
}