advent22/ui/src/components/bulma/Drawer.vue

84 lines
2 KiB
Vue

<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="card">
<header class="card-header is-unselectable" style="cursor: pointer">
<p class="card-header-title" @click="toggle">{{ header }}</p>
<p v-if="refreshable && is_open" class="card-header-icon px-0">
<BulmaButton class="is-small is-primary" @click="load">
<FontAwesomeIcon
:icon="['fas', 'arrows-rotate']"
:spin="state === 'loading'"
/>
</BulmaButton>
</p>
<button class="card-header-icon" @click="toggle">
<span class="icon">
<FontAwesomeIcon
:icon="['fas', is_open ? 'angle-down' : 'angle-right']"
/>
</span>
</button>
</header>
<slot v-if="state === 'loading'" name="loading">
<div class="card-content">
<progress class="progress is-primary" />
</div>
</slot>
<slot v-else-if="state === 'err'" name="error">
<div class="card-content has-text-danger has-text-centered">
<span class="icon is-large">
<FontAwesomeIcon :icon="['fas', 'ban']" size="3x" />
</span>
</div>
</slot>
<slot v-else-if="state === 'ok'" name="default" />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import BulmaButton from "./Button.vue";
const props = withDefaults(
defineProps<{
header: string;
opening?: () => Promise<void>;
refreshable?: boolean;
}>(),
{ opening: async () => {}, refreshable: false },
);
const state = ref<"closed" | "loading" | "ok" | "err">("closed");
const is_open = computed(() => state.value !== "closed");
async function toggle(): Promise<void> {
if (is_open.value) {
state.value = "closed";
} else {
await load();
}
}
async function load(): Promise<void> {
state.value = "loading";
try {
await props.opening();
state.value = "ok";
} catch {
state.value = "err";
}
}
</script>
<style scoped>
div.card:not(:last-child) {
margin-bottom: 1.5rem;
}
</style>