cobalt / web /src /components /save /SupportedServices.svelte
playingapi's picture
Upload 376 files
43a06dc verified
<script lang="ts">
import { t } from "$lib/i18n/translations";
import { getServerInfo } from "$lib/api/server-info";
import cachedInfo from "$lib/state/server-info";
import Skeleton from "$components/misc/Skeleton.svelte";
import IconPlus from "@tabler/icons-svelte/IconPlus.svelte";
let services: string[] = [];
let popover: HTMLDivElement;
$: expanded = false;
$: loaded = false;
$: renderPopover = false;
const loadInfo = async () => {
await getServerInfo();
if ($cachedInfo) {
loaded = true;
services = $cachedInfo.info.cobalt.services;
}
};
const popoverAction = async () => {
expanded = !expanded;
if (expanded && services.length === 0) {
await loadInfo();
}
if (expanded) {
popover.focus();
}
}
const showPopover = async () => {
const timeout = !renderPopover;
renderPopover = true;
// 10ms delay to let the popover render for the first time
if (timeout) {
setTimeout(popoverAction, 10);
} else {
await popoverAction();
}
};
</script>
<div id="supported-services" class:expanded>
<button
id="services-button"
on:click={showPopover}
aria-label={$t(`save.services.title_${expanded ? "hide" : "show"}`)}
>
<div class="expand-icon">
<IconPlus />
</div>
<span class="title">{$t("save.services.title")}</span>
</button>
{#if renderPopover}
<div id="services-popover">
<div
id="services-container"
bind:this={popover}
tabindex="-1"
data-focus-ring-hidden
>
{#if loaded}
{#each services as service}
<div class="service-item">{service}</div>
{/each}
{:else}
{#each { length: 17 } as _}
<Skeleton
class="elevated"
width={Math.random() * 44 + 50 + "px"}
height="24.5px"
/>
{/each}
{/if}
</div>
<div id="services-disclaimer" class="subtext">
{$t("save.services.disclaimer")}
</div>
</div>
{/if}
</div>
<style>
#supported-services {
display: flex;
position: relative;
max-width: 400px;
flex-direction: column;
align-items: center;
height: 35px;
}
#services-popover {
display: flex;
flex-direction: column;
border-radius: 18px;
background: var(--button);
box-shadow:
var(--button-box-shadow),
0 0 10px 10px var(--popover-glow);
position: relative;
padding: 12px;
gap: 6px;
top: 6px;
opacity: 0;
transform: scale(0);
transform-origin: top center;
transition:
transform 0.3s cubic-bezier(0.53, 0.05, 0.23, 1.15),
opacity 0.25s cubic-bezier(0.53, 0.05, 0.23, 0.99);
}
.expanded #services-popover {
transform: scale(1);
opacity: 1;
}
#services-button {
gap: 9px;
padding: 7px 13px 7px 10px;
justify-content: flex-start;
border-radius: 18px;
display: flex;
flex-direction: row;
font-size: 13px;
font-weight: 500;
background: none;
}
#services-button:not(:focus-visible) {
box-shadow: none;
}
.expand-icon {
height: 22px;
width: 22px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 18px;
background: var(--button-elevated);
padding: 0;
box-shadow: none;
transition: transform 0.2s;
}
#services-button:active .expand-icon {
background: var(--button-elevated-hover);
}
@media (hover: hover) {
#services-button:hover .expand-icon {
background: var(--button-elevated-hover);
}
}
.expand-icon :global(svg) {
height: 18px;
width: 18px;
stroke-width: 2px;
color: var(--secondary);
will-change: transform;
}
.expanded .expand-icon {
transform: rotate(45deg);
}
#services-container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 3px;
}
.service-item {
display: flex;
padding: 4px 8px;
border-radius: calc(var(--border-radius) / 2);
background: var(--button-elevated);
font-size: 12.5px;
font-weight: 500;
}
#services-disclaimer {
padding: 0;
user-select: none;
-webkit-user-select: none;
}
.expanded #services-disclaimer {
padding: 0;
user-select: text;
-webkit-user-select: text;
}
@media screen and (max-width: 535px) {
.expand-icon {
height: 21px;
width: 21px;
}
.expand-icon :global(svg) {
height: 16px;
width: 16px;
}
}
</style>