funkwhale/front/src/components/ui/Button.vue

172 lines
3.4 KiB
Vue

<script setup lang="ts">
import { ref, computed, useSlots, onMounted } from 'vue'
import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, color } from '~/composables/color';
import { type WidthProps, width } from '~/composables/width'
import { type AlignmentProps, align } from '~/composables/alignment'
import Loader from '~/components/ui/Loader.vue'
const props = defineProps<{
thinFont?: true
lowHeight? : true
isActive?: boolean
isLoading?: boolean
shadow?: boolean
round?: boolean
icon?: string
onClick?: (...args: any[]) => void | Promise<void>
autofocus? : boolean
ariaPressed? : true
} & (ColorProps | DefaultProps)
& VariantProps
& RaisedProps
& WidthProps
& AlignmentProps>()
const slots = useSlots()
const isIconOnly = computed(() => !!props.icon && !slots.default)
const internalLoader = ref(false)
const isLoading = computed(() => props.isLoading || internalLoader.value)
const fontWeight = props.thinFont ? 400 : 900
const button = ref()
const click = async (...args: any[]) => {
internalLoader.value = true
try {
await props.onClick?.(...args)
} finally {
internalLoader.value = false
}
}
onMounted(() => {
if (props.autofocus) button.value.focus();
})
</script>
<template>
<button ref="button"
v-bind="color(props, ['interactive'])(
width(props, isIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
align(props, { alignSelf:'start', alignText:'center' })(
)))"
class="funkwhale button"
:aria-pressed="props.ariaPressed"
:class="{
'is-loading': isLoading,
'is-icon-only': isIconOnly,
'has-icon': !!icon,
'is-round': round,
'is-shadow': shadow,
}"
@click="click"
>
<i v-if="icon" :class="['bi', icon]" />
<span>
<slot />
</span>
<Loader v-if="isLoading" :container="false" />
</button>
</template>
<style lang="scss">
.funkwhale {
&.button {
// Layout
--padding: 16px;
--shift-by: 0.5px;
position: relative;
display: inline-flex;
white-space: nowrap;
justify-content: space-between;
align-items: center;
padding: calc(var(--padding) / 2 - var(--shift-by)) var(--padding) calc(var(--padding) / 2 + var(--shift-by)) var(--padding);
&.is-icon-only {
padding: var(--padding);
}
// Font
font-family: $font-main;
font-weight: v-bind(fontWeight);
font-size: 14px;
line-height: 14px;
// Decoration
transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
transition: all .2s ease;
&.is-shadow {
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
}
border-radius: var(--fw-border-radius);
&.is-round {
border-radius: 100vh;
}
// States
&[disabled] {
font-weight: normal;
pointer-events: none;
}
&.is-loading {
@extend :active;
> span {
opacity: 0;
}
}
// Content
> span {
position: relative;
top: calc(0px - var(--shift-by));
}
// Icon
i.bi {
font-size: 18px;
margin: -2px 0;
&.large {
font-size: 32px;
margin: -8px 0;
}
}
i.bi + span:not(:empty) {
margin-left: 10px;
}
&.is-icon-only i.bi {
margin: -6px;
&.large {
margin: -8px;
}
}
}
}
</style>