125 lines
2.8 KiB
Vue
125 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"
|
|
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 {RouteLocationNamedRaw} from 'vue-router'
|
|
|
|
export interface BaseButtonProps extends HTMLAttributes {
|
|
type?: BaseButtonTypes
|
|
disabled?: boolean
|
|
to?: RouteLocationNamedRaw
|
|
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>
|