UNPKG

@memori.ai/memori-react

Version:

[![npm version](https://img.shields.io/github/package-json/v/memori-ai/memori-react)](https://www.npmjs.com/package/@memori.ai/memori-react) ![Tests](https://github.com/memori-ai/memori-react/workflows/CI/badge.svg?branch=main) ![TypeScript Support](https

182 lines 10.6 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Button, Popover, useAlertManager, createAlertOptions, } from '@memori.ai/ui'; import { MapPin, Pencil } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { getUncertaintyByViewport } from '../../helpers/venue'; import { useDebounceFn } from '../../helpers/utils'; import { VenueCombobox, VenueMapPreview, getPlaceName, } from '../VenueWidget/VenueWidget'; import cx from 'classnames'; function hasShareableCoords(v) { return !!((v === null || v === void 0 ? void 0 : v.latitude) && (v === null || v === void 0 ? void 0 : v.longitude)); } export const PositionPopoverContent = ({ venue, setVenue, }) => { var _a; const { t } = useTranslation(); const { add } = useAlertManager(); const [geolocationLoading, setGeolocationLoading] = useState(false); const [editingLocation, setEditingLocation] = useState(false); const [permissionDeniedMessage, setPermissionDeniedMessage] = useState(null); const [geocodingError, setGeocodingError] = useState(null); const [fetching, setFetching] = useState(false); const [query, setQuery] = useState(''); const [suggestions, setSuggestions] = useState([]); const geoGenRef = useRef(0); const autocompleteInputRef = useRef(null); const sharingActive = geolocationLoading || hasShareableCoords(venue); const handleSearch = useDebounceFn(async (value) => { setFetching(true); try { const response = await fetch(`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(value)}&format=jsonv2&limit=5&addressdetails=1`); const data = await response.json(); setSuggestions(data); } catch (_a) { setGeocodingError(String(t('widget.geocodingFailed'))); } finally { setFetching(false); } }, 1000); const onQueryChange = useCallback((value, opts) => { setQuery(value); if (opts === null || opts === void 0 ? void 0 : opts.skipSearch) return; if (!value.trim()) { setSuggestions([]); return; } handleSearch(value); }, [handleSearch]); const handleAutocompletePick = useCallback((value) => { const placeName = getPlaceName(value); setVenue({ latitude: value.lat, longitude: value.lon, placeName, uncertainty: (value === null || value === void 0 ? void 0 : value.boundingbox) ? getUncertaintyByViewport(value.boundingbox) : 2, }); setEditingLocation(false); setQuery(''); setSuggestions([]); setGeocodingError(null); setPermissionDeniedMessage(null); }, [setVenue, t]); const startGeolocation = useCallback(() => { const gen = ++geoGenRef.current; setPermissionDeniedMessage(null); setGeocodingError(null); setGeolocationLoading(true); navigator.geolocation.getCurrentPosition(async (pos) => { if (gen !== geoGenRef.current) return; let next = { latitude: pos.coords.latitude, longitude: pos.coords.longitude, placeName: '', uncertainty: pos.coords.accuracy / 1000, }; try { const result = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${pos.coords.latitude}&lon=${pos.coords.longitude}&format=jsonv2&addressdetails=1`); const response = (await result.json()); const placeName = getPlaceName(response); next = { latitude: pos.coords.latitude, longitude: pos.coords.longitude, placeName, uncertainty: pos.coords.accuracy / 1000, }; setVenue(next); } catch (e) { console.error('[PositionPopover] reverse geocode failed', e); setGeocodingError(String(t('widget.geocodingFailed'))); setVenue(next); } finally { if (gen === geoGenRef.current) { setGeolocationLoading(false); add(createAlertOptions({ description: t('widget.positionSharingEnabled') || 'Position sharing has been enabled.', severity: 'success', })); } } }, err => { if (gen !== geoGenRef.current) return; setGeolocationLoading(false); setVenue(undefined); const code = err.code; if (code === 1) { setPermissionDeniedMessage(String(t('widget.positionUnavailableManual'))); setEditingLocation(true); } else { setGeocodingError(String(t('widget.geocodingFailed'))); } }, { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }); }, [setVenue, t, add]); const toggleSharing = useCallback(() => { setPermissionDeniedMessage(null); setGeocodingError(null); if (sharingActive && !geolocationLoading) { geoGenRef.current += 1; setVenue({ latitude: 0, longitude: 0, placeName: '', uncertainty: 0, }); setEditingLocation(false); setQuery(''); setSuggestions([]); return; } if (geolocationLoading) { geoGenRef.current += 1; setGeolocationLoading(false); setVenue(undefined); return; } startGeolocation(); }, [geolocationLoading, setVenue, sharingActive, startGeolocation]); useEffect(() => { if (!editingLocation) return; const id = requestAnimationFrame(() => { var _a; (_a = autocompleteInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }); return () => cancelAnimationFrame(id); }, [editingLocation]); const inlineError = permissionDeniedMessage || geocodingError; return (_jsxs(_Fragment, { children: [_jsx("div", { className: "memori-position-popover__row memori-position-popover__switch-row", children: _jsxs("button", { type: "button", className: "memori-dropdown--auth-row", onClick: () => { toggleSharing(); }, children: [_jsx("span", { className: "memori-dropdown--auth-icon-wrap", children: _jsx(MapPin, { size: 16 }) }), _jsxs("span", { className: "memori-dropdown--auth-copy", children: [_jsx("span", { className: "memori-dropdown--auth-title", children: t('widget.shareLocation') || 'Share my location' }), _jsx("span", { className: "memori-dropdown--auth-subtitle", children: t('widget.shareLocationHint') || 'Get answers tailored to where you are' })] }), _jsx("span", { className: cx('memori-dropdown--switch', sharingActive && 'memori-dropdown--switch--on'), "aria-hidden": true })] }) }), (sharingActive || geolocationLoading || editingLocation) && (_jsx("div", { className: "memori-position-popover__tag-block", children: geolocationLoading ? (_jsxs("div", { className: "memori-position-popover__tag memori-position-popover__tag--loading", "aria-busy": "true", "aria-live": "polite", children: [_jsx("span", { className: "memori-position-popover__spinner", "aria-hidden": true }), _jsx("span", { className: "memori-position-popover__tag-skeleton" })] })) : editingLocation ? (_jsx("div", { className: "memori-position-popover__autocomplete-wrap", children: _jsx(VenueCombobox, { venue: venue, query: query, fetching: fetching, suggestions: suggestions, onQueryChange: onQueryChange, onChange: handleAutocompletePick, getPlaceName: getPlaceName, t: t, autocompleteRootId: "memori-position-popover-venue-search", inputRef: autocompleteInputRef }) })) : (_jsxs("div", { className: "memori-position-popover__tag", children: [_jsx("span", { className: "memori-position-popover__tag-text", children: ((_a = venue === null || venue === void 0 ? void 0 : venue.placeName) === null || _a === void 0 ? void 0 : _a.trim()) ? venue.placeName : t('widget.positionResolving') }), _jsx(Button, { type: "button", variant: "ghost", icon: _jsx(Pencil, { size: 16, strokeWidth: 2, "aria-hidden": true }), size: "sm", className: "memori-position-popover__tag-edit", "aria-label": String(t('widget.editPositionAria')), onClick: () => { setEditingLocation(true); setPermissionDeniedMessage(null); } })] })) })), inlineError && (_jsx("p", { className: "memori-position-popover__error", role: "alert", children: inlineError })), hasShareableCoords(venue) && !geolocationLoading && (_jsx("div", { className: "memori-position-popover__map", children: _jsx(VenueMapPreview, { venue: venue }) }))] })); }; const PositionPopover = ({ venue, setVenue, open, onOpenChange, triggerClassName, triggerButtonVariant = 'primary', triggerAriaLabel, positionerClassName, }) => { const sharingActive = hasShareableCoords(venue); return (_jsx(Popover, { open: open, onOpenChange: onOpenChange, modal: false, closable: false, placement: "bottom-end", sideOffset: 8, contentClassName: "memori-position-popover__popup", slotProps: { trigger: { className: triggerClassName, render: (props) => { return (_jsx(Button, { ...props, type: "button", variant: triggerButtonVariant, className: cx('memori-header--button', 'memori-header--button--position', sharingActive && 'memori-header--button--position--active', open && 'memori-button--active'), "aria-label": triggerAriaLabel, "aria-expanded": open, icon: _jsx(MapPin, { "aria-hidden": true }) })); }, }, positioner: { className: cx('memori-position-popover__positioner', positionerClassName), }, }, content: _jsx(PositionPopoverContent, { venue: venue, setVenue: setVenue }), children: null })); }; export default PositionPopover; //# sourceMappingURL=PositionPopover.js.map