2022-01-04 19:58:06 +01:00
|
|
|
<template>
|
|
|
|
<component
|
|
|
|
:is="componentNodeName"
|
|
|
|
class="base-button"
|
|
|
|
:class="{ 'base-button--type-button': isButton }"
|
|
|
|
v-bind="elementBindings"
|
|
|
|
:disabled="disabled || undefined"
|
2022-07-11 17:06:18 +02:00
|
|
|
ref="button"
|
2022-01-04 19:58:06 +01:00
|
|
|
>
|
2022-08-15 23:08:18 +02:00
|
|
|
<slot/>
|
2022-01-04 19:58:06 +01:00
|
|
|
</component>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
2022-07-05 00:11:12 +02:00
|
|
|
export default { inheritAttrs: false }
|
2022-01-04 19:58:06 +01:00
|
|
|
</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 (see the
|
|
|
|
// componentNodeName and elementBindings ref for this).
|
|
|
|
|
|
|
|
// NOTE: Do NOT use buttons with @click to push routes. => Use router-links instead!
|
|
|
|
|
2022-07-30 17:51:09 +02:00
|
|
|
import { ref, watchEffect, computed, useAttrs, type PropType } from 'vue'
|
2022-01-04 19:58:06 +01:00
|
|
|
|
2022-08-15 23:08:18 +02:00
|
|
|
const BASE_BUTTON_TYPES_MAP = Object.freeze({
|
|
|
|
button: 'button',
|
|
|
|
submit: 'submit',
|
2022-01-04 19:58:06 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
type BaseButtonTypes = keyof typeof BASE_BUTTON_TYPES_MAP
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
type: {
|
|
|
|
type: String as PropType<BaseButtonTypes>,
|
|
|
|
default: 'button',
|
|
|
|
},
|
|
|
|
disabled: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const componentNodeName = ref<Node['nodeName']>('button')
|
2022-08-15 23:08:18 +02:00
|
|
|
|
2022-01-04 19:58:06 +01:00
|
|
|
interface ElementBindings {
|
|
|
|
type?: string;
|
2022-06-22 21:46:50 +02:00
|
|
|
rel?: string;
|
|
|
|
target?: string;
|
2022-01-04 19:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const elementBindings = ref({})
|
|
|
|
|
|
|
|
const attrs = useAttrs()
|
|
|
|
watchEffect(() => {
|
|
|
|
// by default this component is a button element with the attribute of the type "button" (default prop value)
|
|
|
|
let nodeName = 'button'
|
|
|
|
let bindings: ElementBindings = {type: props.type}
|
|
|
|
|
|
|
|
// if we find a "to" prop we set it as router-link
|
|
|
|
if ('to' in attrs) {
|
|
|
|
nodeName = 'router-link'
|
|
|
|
bindings = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there is a href we assume the user wants an external link via a link element
|
2022-02-06 22:57:32 +01:00
|
|
|
// we also set a predefined value for the attribute rel, but make it possible to overwrite this by the user.
|
2022-01-04 19:58:06 +01:00
|
|
|
if ('href' in attrs) {
|
|
|
|
nodeName = 'a'
|
2022-06-22 21:46:50 +02:00
|
|
|
bindings = {
|
|
|
|
rel: 'noreferrer noopener nofollow',
|
|
|
|
target: '_blank',
|
|
|
|
}
|
2022-01-04 19:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
componentNodeName.value = nodeName
|
|
|
|
elementBindings.value = {
|
|
|
|
...bindings,
|
|
|
|
...attrs,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const isButton = computed(() => componentNodeName.value === 'button')
|
2022-07-11 17:06:18 +02:00
|
|
|
|
|
|
|
const button = ref()
|
2022-08-15 23:08:18 +02:00
|
|
|
|
2022-07-11 17:06:18 +02:00
|
|
|
function focus() {
|
|
|
|
button.value.focus()
|
|
|
|
}
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
focus,
|
|
|
|
})
|
2022-01-04 19:58:06 +01:00
|
|
|
</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;
|
2022-05-11 01:13:36 +02:00
|
|
|
display: inline-block;
|
2022-01-04 19:58:06 +01:00
|
|
|
color: inherit;
|
|
|
|
font: inherit;
|
|
|
|
user-select: none;
|
|
|
|
pointer-events: auto; // disable possible resets
|
|
|
|
|
2022-08-15 23:08:18 +02:00
|
|
|
&:focus, &.is-focused {
|
2022-01-04 19:58:06 +01:00
|
|
|
outline: transparent;
|
|
|
|
}
|
|
|
|
|
|
|
|
&[disabled] {
|
|
|
|
cursor: default;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|