This repository has been archived on 2024-02-08. You can view files and clone it, but cannot push or open issues or pull requests.
frontend/src/components/base/BaseButton.vue

126 lines
2.8 KiB
Vue

<!-- a disabled link of any kind is not a link -->
<!-- we have a router link -->
<!-- just a normal link -->
<!-- a button it shall be -->
<!-- note that we only pass the click listener here -->
<template>
<div
v-if="disabled === true && (to !== undefined || href !== undefined)"
class="base-button"
:aria-disabled="disabled || undefined"
ref="button"
>
<slot/>
</div>
<router-link
v-else-if="to !== undefined"
:to="to"
class="base-button"
ref="button"
>
<slot/>
</router-link>
<a v-else-if="href !== undefined"
class="base-button"
:href="href"
rel="noreferrer noopener nofollow"
target="_blank"
ref="button"
>
<slot/>
</a>
<button
v-else
:type="type"
class="base-button base-button--type-button"
:disabled="disabled || undefined"
ref="button"
@click="(event: MouseEvent) => emit('click', event)"
>
<slot/>
</button>
</template>
<script lang="ts">
const BASE_BUTTON_TYPES_MAP = {
BUTTON: 'button',
SUBMIT: 'submit',
} as const
export type BaseButtonTypes = typeof BASE_BUTTON_TYPES_MAP[keyof typeof BASE_BUTTON_TYPES_MAP] | undefined
</script>
<script lang="ts" setup>
// this component removes styling differences between links / vue-router links and button elements
// by doing so we make it easy abstract the functionality from style and enable easier and semantic
// correct button and link usage. Also see: https://css-tricks.com/a-complete-guide-to-links-and-buttons/#accessibility-considerations
// the component tries to heuristically determine what it should be checking the props
// NOTE: Do NOT use buttons with @click to push routes. => Use router-links instead!
import {unrefElement} from '@vueuse/core'
import {ref, type HTMLAttributes} from 'vue'
import type {RouteLocationRaw} from 'vue-router'
export interface BaseButtonProps extends HTMLAttributes {
type?: BaseButtonTypes
disabled?: boolean
to?: RouteLocationRaw
href?: string
}
export interface BaseButtonEmits {
(e: 'click', payload: MouseEvent): void
}
const {
type = BASE_BUTTON_TYPES_MAP.BUTTON,
disabled = false,
} = defineProps<BaseButtonProps>()
const emit = defineEmits<BaseButtonEmits>()
const button = ref<HTMLElement | null>(null)
function focus() {
unrefElement(button)?.focus()
}
defineExpose({
focus,
})
</script>
<style lang="scss">
// NOTE: we do not use scoped styles to reduce specifity and make it easy to overwrite
// We reset the default styles of a button element to enable easier styling
:where(.base-button--type-button) {
border: 0;
margin: 0;
padding: 0;
text-decoration: none;
background-color: transparent;
text-align: center;
appearance: none;
}
:where(.base-button) {
cursor: pointer;
display: inline-block;
color: inherit;
font: inherit;
user-select: none;
pointer-events: auto; // disable possible resets
&:focus, &.is-focused {
outline: transparent;
}
&[disabled] {
cursor: default;
}
}
</style>