import clsx from "clsx"; import debounce from "lodash/debounce"; import { useTranslation } from "next-i18next"; import * as React from "react"; import { useMeasure } from "react-use"; import smoothscroll from "smoothscroll-polyfill"; import { decodeDateOption } from "../../utils/date-time-utils"; import Button from "../button"; import DateCard from "../date-card"; import ArrowLeft from "../icons/arrow-left.svg"; import ArrowRight from "../icons/arrow-right.svg"; import PlusCircle from "../icons/plus-circle.svg"; import TimeZonePicker from "../time-zone-picker"; import { TransitionPopInOut } from "../transitions"; import { usePoll } from "../use-poll"; import { useAddParticipantMutation } from "./mutations"; import ParticipantRow from "./participant-row"; import ParticipantRowForm from "./participant-row-form"; import { PollContext, usePollContext } from "./poll-context"; import Score from "./score"; import TimeRange from "./time-range"; import { PollProps } from "./types"; if (typeof window !== "undefined") { smoothscroll.polyfill(); } // There's a bug in Safari 15.4 that causes `scroll` to no work as intended const isSafariV154 = typeof window !== "undefined" ? /Version\/15.[4-9]\sSafari/.test(navigator.userAgent) : false; export const ControlledScrollDiv: React.VoidFunctionComponent<{ children?: React.ReactNode; className?: string; }> = ({ className, children }) => { const { setScrollPosition, availableSpace, scrollPosition } = usePollContext(); const ref = React.useRef(null); const didSetInitialScrollPosition = React.useRef(false); React.useEffect(() => { if (ref.current) { if (!isSafariV154) { ref.current.scroll({ left: scrollPosition, behavior: didSetInitialScrollPosition?.current ? "smooth" : "auto", }); } else { ref.current.scrollLeft = scrollPosition; } didSetInitialScrollPosition.current = true; } }, [scrollPosition]); return (
{ const div = e.target as HTMLDivElement; setScrollPosition(div.scrollLeft); }} > {children}
); }; const minSidebarWidth = 150; const Poll: React.VoidFunctionComponent = ({ pollId, role, timeZone, options, participants, highScore, targetTimeZone, onChangeTargetTimeZone, }) => { const { t } = useTranslation("app"); const [ref, { width }] = useMeasure(); const [editingParticipantId, setEditingParticipantId] = React.useState(null); const actionColumnWidth = 160; const columnWidth = Math.min( 100, Math.max( 95, (width - minSidebarWidth - actionColumnWidth) / options.length, ), ); const numberOfVisibleColumns = Math.min( options.length, Math.floor((width - (minSidebarWidth + actionColumnWidth)) / columnWidth), ); const sidebarWidth = width - (numberOfVisibleColumns * columnWidth + actionColumnWidth); const availableSpace = Math.min( numberOfVisibleColumns * columnWidth, options.length * columnWidth, ); const [activeOptionId, setActiveOptionId] = React.useState(null); const [scrollPosition, setScrollPosition] = React.useState(0); const maxScrollPosition = columnWidth * options.length - columnWidth * numberOfVisibleColumns; const debouncedSetScrollPosition = React.useMemo( () => debounce(setScrollPosition, 200), [], ); const numberOfInvisibleColumns = options.length - numberOfVisibleColumns; const [didUsePagination, setDidUsePagination] = React.useState(false); const [shouldShowNewParticipantForm, setShouldShowNewParticipantForm] = React.useState(participants.length === 0); const pollWidth = sidebarWidth + options.length * columnWidth + actionColumnWidth; const { mutate: addParticipant } = useAddParticipantMutation(pollId); const goToNextPage = () => { debouncedSetScrollPosition( Math.min( maxScrollPosition, scrollPosition + numberOfVisibleColumns * columnWidth, ), ); }; const goToPreviousPage = () => { setScrollPosition( Math.max(0, scrollPosition - numberOfVisibleColumns * columnWidth), ); }; const poll = usePoll(); return (
{role !== "readOnly" ? (
{timeZone ? (
{t("timeZone")}
) : null}
{!shouldShowNewParticipantForm && !poll.closed ? ( ) : null}
) : null}
{t("participantCount", { count: participants.length })}
0}>
{options.map((option) => { const parsedOption = decodeDateOption( option.value, timeZone, targetTimeZone, ); const numVotes = option.votes.length; return (
setActiveOptionId(option.id)} onMouseOut={() => setActiveOptionId(null)} >
} />
{parsedOption.type === "timeSlot" ? ( ) : null}
); })}
{shouldShowNewParticipantForm ? ( { return new Promise((resolve, reject) => { addParticipant(data, { onSuccess: () => { setShouldShowNewParticipantForm(false); resolve(); }, onError: reject, }); }); }} options={options} onCancel={() => { setShouldShowNewParticipantForm(false); }} /> ) : null}
{participants.map((participant, i) => { return ( { setEditingParticipantId(isEditing ? participant.id : null); }} /> ); })}
); }; export default React.memo(Poll);