Skip to content

Commit

Permalink
Merge pull request #71 from zsh-eng/update-swipe
Browse files Browse the repository at this point in the history
Fix issues with swiping and overflow
  • Loading branch information
zsh-eng authored Apr 25, 2024
2 parents a30d9fb + 4124aa8 commit aaf391b
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/components/flashcard/main/answer-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function AnswerButtons({
return (
<div
className={cn(
"grid h-full grid-cols-2 gap-x-2 gap-y-2 sm:h-12 sm:w-96 md:grid-cols-4",
"grid h-full grid-cols-2 gap-x-2 gap-y-2 shadow-sm sm:h-12 sm:w-96 md:grid-cols-4",
)}
>
{open ? (
Expand All @@ -85,7 +85,7 @@ export default function AnswerButtons({
) : (
<Button
variant="secondary"
className="col-span-2 h-32 text-2xl sm:h-full sm:text-lg md:col-span-4"
className="col-span-2 h-32 text-2xl transition duration-300 hover:scale-105 sm:h-full sm:text-lg md:col-span-4"
onClick={() => setOpen(true)}
>
Reveal
Expand Down
4 changes: 2 additions & 2 deletions src/components/flashcard/main/editable-flashcard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function EditableFlashcard({ form, setOpen, open, editing }: Props) {
<Form {...form}>
<div
className={cn(
"col-span-8 flex h-full min-h-80 w-full items-center justify-center overflow-y-auto border border-input sm:col-span-4 sm:min-h-96",
"col-span-8 flex h-full min-h-80 w-full items-center justify-center overflow-y-auto rounded-md border border-input sm:col-span-4 sm:min-h-96",
editing ? "bg-muted" : "",
)}
onClick={onContainerFocus}
Expand All @@ -35,7 +35,7 @@ export function EditableFlashcard({ form, setOpen, open, editing }: Props) {

<div
className={cn(
"relative col-span-8 flex h-full min-h-80 w-full items-center justify-center border border-input sm:col-span-4 sm:min-h-96",
"relative col-span-8 flex h-full min-h-80 w-full items-center justify-center rounded-md border border-input sm:col-span-4 sm:min-h-96",
editing ? "bg-muted" : "",
)}
onClick={onContainerFocus}
Expand Down
96 changes: 75 additions & 21 deletions src/components/flashcard/main/flashcard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Props = {
onDelete: () => void;
};

const SWIPE_THRESHOLD = 120;
const SWIPE_THRESHOLD = 60;
const SWIPE_PADDING = 60;
const ANIMATION_DURATION = 200;
const SWIPE_DURATION = ANIMATION_DURATION + 500;
Expand All @@ -45,6 +45,52 @@ const targetIsHTMLElement = (
return target instanceof HTMLElement;
};

function replaceCardWithPlaceholder(
card: HTMLElement,
placeholder: HTMLElement,
) {
const rect = card.getBoundingClientRect();
const parentNode = card.parentNode as HTMLElement;
const parentRect = parentNode.getBoundingClientRect();

// Position the target element correctly
card.style.left = `${rect.left - parentRect.left}px`;
card.style.top = `${rect.top - parentRect.top}px`;
// Set up the placeholder element to be same size as the target element
placeholder.style.height = `${rect.height}px`;
placeholder.style.display = "block";
card.parentNode?.insertBefore(placeholder, card);
// Set up the target element
card.style.transition = "transform 0.05s";
card.style.position = "absolute";
// Ensure that the target element has the same size
card.style.width = `${rect.width}px`;
card.style.height = `${rect.height}px`;
}

function revertCardFromPlaceholder(
card: HTMLElement,
placeholder: HTMLElement,
) {
placeholder.style.height = "0px";
placeholder.style.display = "none";
card.style.position = "static";
}

function translateCardToThreshold(card: HTMLElement, x: number, y: number) {
const absX = Math.abs(x);
const transformAbsDistance =
Math.floor(absX) + absX > SWIPE_THRESHOLD ? SWIPE_PADDING : 0;
const transformDistance =
x > 0 ? transformAbsDistance : -transformAbsDistance;
card.style.transform = `translateX(${transformDistance}px)`;
}

function revertCardFromTranslation(card: HTMLElement) {
card.style.transition = `transform ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1)`;
card.style.transform = "translateX(0)";
}

/**
* Flashcard is the component that displays a {@link Card}
*/
Expand All @@ -60,7 +106,9 @@ export default function Flashcard({
const { card_contents: initialCardContent } = sessionCard;
const [open, setOpen] = useState(false);
const [editing, setEditing] = useState(false);
const cardRef = useRef<HTMLDivElement>(null);
const cardContainerRef = useRef<HTMLDivElement>(null);
const placeholderRef = useRef<HTMLDivElement>(null);

const answerButtonsContainerRef = useRef<HTMLDivElement>(null);
const isMobile = useMediaQuery("only screen and (max-width: 640px)");

Expand Down Expand Up @@ -91,30 +139,27 @@ export default function Flashcard({
const handlers = useSwipeable({
onSwipeStart: (eventData) => {
const target = eventData.event.currentTarget;
if (!targetIsHTMLElement(target)) return;
target.style.transition = "transform 0.05s";
const placeholderElement = placeholderRef.current;
if (!targetIsHTMLElement(target) || !placeholderElement) return;

replaceCardWithPlaceholder(target, placeholderElement);

const id = setTimeout(() => {
setCurrentlyFocusedRating(undefined);
}, SWIPE_DURATION - 100);
const id = setTimeout(
() => setCurrentlyFocusedRating(undefined),
SWIPE_DURATION - 100,
);
setTimeoutId(id);
},
onSwiping: (eventData) => {
const target = eventData.event.currentTarget;
if (!targetIsHTMLElement(target)) return;

const { deltaX: x, deltaY: y } = eventData;
const absX = Math.abs(x);
const transformAbsDistance =
Math.floor(absX / 4) + absX > SWIPE_THRESHOLD ? SWIPE_PADDING : 0;
const transformDistance =
x > 0 ? transformAbsDistance : -transformAbsDistance;
target.style.transform = `translateX(${transformDistance}px)`;
translateCardToThreshold(target, x, y);

if (x > SWIPE_THRESHOLD) {
setCurrentlyFocusedRating("Easy");
}

if (x < -SWIPE_THRESHOLD) {
setCurrentlyFocusedRating("Hard");
}
Expand All @@ -126,10 +171,14 @@ export default function Flashcard({
}

const target = eventData.event.currentTarget;
if (!targetIsHTMLElement(target)) return;
const placeholderElement = placeholderRef.current;
if (!targetIsHTMLElement(target) || !placeholderElement) return;
revertCardFromTranslation(target);

target.style.transition = `transform ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1)`;
target.style.transform = "translateX(0)";
setTimeout(
() => revertCardFromPlaceholder(target, placeholderElement),
ANIMATION_DURATION,
);
currentlyFocusedRating !== "Good" && setCurrentlyFocusedRating(undefined);
},
onSwipedRight: () => {
Expand All @@ -153,7 +202,7 @@ export default function Flashcard({

useKeydownRating(onRating, open && !editing, () => setOpen(true));
useClickOutside({
ref: cardRef,
ref: cardContainerRef,
enabled: editing,
callback: () => {
handleEdit();
Expand All @@ -170,8 +219,8 @@ export default function Flashcard({

return (
<div
className="relative col-span-12 flex flex-col gap-x-4 gap-y-2"
ref={cardRef}
className="relative col-span-12 flex flex-col gap-x-4 gap-y-2 overflow-hidden"
ref={cardContainerRef}
>
{currentlyFocusedRating === "Good" && (
<ThumbsUp className="absolute bottom-0 left-0 right-0 top-0 z-20 mx-auto my-auto h-12 w-12 animate-tada text-primary" />
Expand Down Expand Up @@ -214,8 +263,9 @@ export default function Flashcard({
onDelete={onDelete}
onUndo={() => history.undo()}
/>

<div
className="col-span-8 grid grid-cols-8 place-items-end gap-x-4 gap-y-4 bg-background"
className="col-span-8 grid grid-cols-8 place-items-end gap-x-4 gap-y-2 bg-background"
{...handlers}
onDoubleClick={() => {
if (!open || editing || currentlyFocusedRating || !isMobile) return;
Expand All @@ -233,6 +283,10 @@ export default function Flashcard({
editing={editing}
/>
</div>
<div
className="-z-40 col-span-12 hidden bg-muted opacity-60 shadow-inner"
ref={placeholderRef}
></div>

<div
className="z-20 mb-6 w-full sm:static sm:mx-auto sm:w-max"
Expand Down
2 changes: 1 addition & 1 deletion src/components/flashcard/main/swipe-action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function SwipeActionText({ direction, active, children }: Props) {
>
<div
className={cn(
"rounded-md bg-background px-8 py-4 text-muted transition duration-300",
"rounded-md px-8 py-4 text-muted transition duration-300",
active && "animate-wiggle text-primary",
)}
>
Expand Down

0 comments on commit aaf391b

Please sign in to comment.