web/DonateOptionsCard: add scroll buttons to the options container

cuz users without touchpads couldn't scroll it without tabbing
This commit is contained in:
wukko 2024-09-27 20:54:09 +06:00
parent 5a4be4890b
commit 6ba27f8369
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2

View file

@ -1,5 +1,8 @@
<script lang="ts"> <script lang="ts">
import settings from "$lib/state/settings";
import { donate } from "$lib/env"; import { donate } from "$lib/env";
import { device } from "$lib/device";
import { t } from "$lib/i18n/translations"; import { t } from "$lib/i18n/translations";
import DonateCardContainer from "$components/donate/DonateCardContainer.svelte"; import DonateCardContainer from "$components/donate/DonateCardContainer.svelte";
@ -11,7 +14,6 @@
import IconPizza from "@tabler/icons-svelte/IconPizza.svelte"; import IconPizza from "@tabler/icons-svelte/IconPizza.svelte";
import IconToolsKitchen2 from "@tabler/icons-svelte/IconToolsKitchen2.svelte"; import IconToolsKitchen2 from "@tabler/icons-svelte/IconToolsKitchen2.svelte";
import IconPaperBag from "@tabler/icons-svelte/IconPaperBag.svelte"; import IconPaperBag from "@tabler/icons-svelte/IconPaperBag.svelte";
import IconArrowRight from "@tabler/icons-svelte/IconArrowRight.svelte";
import IconSoup from "@tabler/icons-svelte/IconSoup.svelte"; import IconSoup from "@tabler/icons-svelte/IconSoup.svelte";
import IconFridge from "@tabler/icons-svelte/IconFridge.svelte"; import IconFridge from "@tabler/icons-svelte/IconFridge.svelte";
import IconArmchair from "@tabler/icons-svelte/IconArmchair.svelte"; import IconArmchair from "@tabler/icons-svelte/IconArmchair.svelte";
@ -20,7 +22,11 @@
import IconPhoto from "@tabler/icons-svelte/IconPhoto.svelte"; import IconPhoto from "@tabler/icons-svelte/IconPhoto.svelte";
import IconWorldWww from "@tabler/icons-svelte/IconWorldWww.svelte"; import IconWorldWww from "@tabler/icons-svelte/IconWorldWww.svelte";
import IconBath from "@tabler/icons-svelte/IconBath.svelte"; import IconBath from "@tabler/icons-svelte/IconBath.svelte";
import OuterLink from "$components/misc/OuterLink.svelte";
import IconArrowLeft from "@tabler/icons-svelte/IconArrowLeft.svelte";
import IconArrowRight from "@tabler/icons-svelte/IconArrowRight.svelte";
let donateList: HTMLElement;
let customInput: HTMLInputElement; let customInput: HTMLInputElement;
let customInputValue: number | null; let customInputValue: number | null;
@ -39,7 +45,7 @@
4900: IconApple, 4900: IconApple,
7398: IconDeviceLaptop, 7398: IconDeviceLaptop,
8629: IconPhoto, 8629: IconPhoto,
9433: IconBath 9433: IconBath,
}; };
type Processor = "stripe" | "liberapay"; type Processor = "stripe" | "liberapay";
@ -66,7 +72,30 @@
} }
const amount = Math.floor(Number(customInputValue) * 100); const amount = Math.floor(Number(customInputValue) * 100);
return window.open(donationMethods[processor](amount), '_blank'); return window.open(donationMethods[processor](amount), "_blank");
};
const scrollBehavior = $settings.appearance.reduceMotion
? "instant"
: "smooth";
$: showLeftScroll = false;
$: showRightScroll = true;
const scroll = (direction: "left" | "right") => {
const currentPos = donateList.scrollLeft;
const newPos = direction === "left" ? currentPos - 150 : currentPos + 150;
const maxPos = donateList.scrollWidth - donateList.getBoundingClientRect().width;
donateList.scroll({
left: newPos,
behavior: scrollBehavior,
});
setTimeout(() => {
showLeftScroll = newPos > 0;
showRightScroll = newPos < maxPos && newPos !== maxPos;
}, 150)
}; };
</script> </script>
@ -84,10 +113,11 @@
<div class="donation-type-text"> <div class="donation-type-text">
<div class="donate-card-title">{$t("donate.card.once")}</div> <div class="donate-card-title">{$t("donate.card.once")}</div>
<div class="donate-card-subtitle"> <div class="donate-card-subtitle">
{$t("donate.card.processor", { value: 'stripe' })} {$t("donate.card.processor", { value: "stripe" })}
</div> </div>
</div> </div>
</button> </button>
<button <button
id="donation-type-monthly" id="donation-type-monthly"
class="donation-type" class="donation-type"
@ -100,13 +130,48 @@
<div class="donation-type-text"> <div class="donation-type-text">
<div class="donate-card-title">{$t("donate.card.monthly")}</div> <div class="donate-card-title">{$t("donate.card.monthly")}</div>
<div class="donate-card-subtitle"> <div class="donate-card-subtitle">
{$t("donate.card.processor", { value: 'liberapay' })} {$t("donate.card.processor", { value: "liberapay" })}
</div> </div>
</div> </div>
</button> </button>
</div> </div>
<div id="donation-options">
{#each Object.entries(PRESET_DONATION_AMOUNTS) as [ amount, component ]} <div
id="donation-options-container"
aria-hidden="true"
class:mask-both={!device.is.mobile && showLeftScroll && showRightScroll}
class:mask-left={!device.is.mobile && showLeftScroll && !showRightScroll}
class:mask-right={!device.is.mobile && showRightScroll && !showLeftScroll}
>
{#if !device.is.mobile}
<div id="donation-options-scroll">
<button
class="scroll-button left"
class:hidden={!showLeftScroll}
on:click={() => {
scroll("left");
}}
>
<IconArrowLeft />
</button>
<button
class="scroll-button right"
class:hidden={!showRightScroll}
on:click={() => {
scroll("right");
}}
>
<IconArrowRight />
</button>
</div>
{/if}
<div
id="donation-options"
bind:this={donateList}
on:scroll={(e) => {}}
>
{#each Object.entries(PRESET_DONATION_AMOUNTS) as [amount, component]}
<DonationOption <DonationOption
price={+amount} price={+amount}
desc={$t(`donate.card.option.${amount}`)} desc={$t(`donate.card.option.${amount}`)}
@ -116,11 +181,14 @@
</DonationOption> </DonationOption>
{/each} {/each}
</div> </div>
</div>
<div id="donation-custom"> <div id="donation-custom">
<div id="input-container" class:focused={customFocused}> <div id="input-container" class:focused={customFocused}>
{#if customInputValue || customInput?.validity.badInput} {#if customInputValue || customInput?.validity.badInput}
<span id="input-dollar-sign">$</span> <span id="input-dollar-sign">$</span>
{/if} {/if}
<input <input
id="donation-custom-input" id="donation-custom-input"
type="number" type="number"
@ -137,6 +205,7 @@
on:keydown={(e) => e.key === "Enter" && sendCustom()} on:keydown={(e) => e.key === "Enter" && sendCustom()}
/> />
</div> </div>
<button <button
id="donation-custom-submit" id="donation-custom-submit"
on:click={sendCustom} on:click={sendCustom}
@ -146,6 +215,7 @@
<IconArrowRight /> <IconArrowRight />
</button> </button>
</div> </div>
<div class="donate-card-subtitle processor-mobile"> <div class="donate-card-subtitle processor-mobile">
{$t("donate.card.processor", { value: processor })} {$t("donate.card.processor", { value: processor })}
</div> </div>
@ -288,6 +358,79 @@
text-align: center; text-align: center;
} }
#donation-options-container {
display: flex;
flex-direction: column;
gap: calc(var(--donate-card-main-padding) / 2);
position: relative;
}
#donation-options-scroll {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
z-index: 3;
overflow: hidden;
opacity: 0;
transition: opacity 0.2s;
}
.scroll-button {
pointer-events: all;
color: white;
padding: 0 16px;
background-color: transparent;
height: 100%;
transition: opacity 0.2s;
}
#donation-options-container:hover #donation-options-scroll {
opacity: 1;
}
.scroll-button.hidden {
opacity: 0;
visibility: hidden;
}
#donation-options-container.mask-both:hover #donation-options {
mask-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 50%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0) 100%
);
}
#donation-options-container.mask-left:hover #donation-options {
mask-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 50%,
rgba(0, 0, 0, 1) 96%,
rgba(0, 0, 0, 0) 100%
);
}
#donation-options-container.mask-right:hover #donation-options {
mask-image: linear-gradient(
90deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 4%,
rgba(0, 0, 0, 1) 50%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0) 100%
);
}
@media screen and (max-width: 550px) { @media screen and (max-width: 550px) {
:global(#donation-box) { :global(#donation-box) {
min-width: unset; min-width: unset;