feat: rework BaseButton
This commit is contained in:
parent
a2c1702eef
commit
e8c6afce72
@ -1,18 +1,52 @@
|
|||||||
|
<!-- 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>
|
<template>
|
||||||
<component
|
<div
|
||||||
:is="componentNodeName"
|
v-if="disabled === true && (to !== undefined || href !== undefined)"
|
||||||
class="base-button"
|
class="base-button"
|
||||||
:class="{ 'base-button--type-button': isButton }"
|
:aria-disabled="disabled || undefined"
|
||||||
v-bind="elementBindings"
|
|
||||||
:disabled="disabled || undefined"
|
|
||||||
ref="button"
|
ref="button"
|
||||||
>
|
>
|
||||||
<slot/>
|
<slot/>
|
||||||
</component>
|
</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>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default { inheritAttrs: false }
|
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>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -20,77 +54,36 @@ export default { inheritAttrs: false }
|
|||||||
// by doing so we make it easy abstract the functionality from style and enable easier and semantic
|
// 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
|
// 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
|
// the component tries to heuristically determine what it should be checking the props
|
||||||
// componentNodeName and elementBindings ref for this).
|
|
||||||
|
|
||||||
// NOTE: Do NOT use buttons with @click to push routes. => Use router-links instead!
|
// NOTE: Do NOT use buttons with @click to push routes. => Use router-links instead!
|
||||||
|
|
||||||
import { ref, watchEffect, computed, useAttrs, type PropType } from 'vue'
|
import {unrefElement} from '@vueuse/core'
|
||||||
|
import {ref, type HTMLAttributes} from 'vue'
|
||||||
|
import type {RouteLocationNamedRaw} from 'vue-router'
|
||||||
|
|
||||||
const BASE_BUTTON_TYPES_MAP = Object.freeze({
|
export interface BaseButtonProps extends HTMLAttributes {
|
||||||
button: 'button',
|
type?: BaseButtonTypes
|
||||||
submit: 'submit',
|
disabled?: boolean
|
||||||
})
|
to?: RouteLocationNamedRaw
|
||||||
|
href?: string
|
||||||
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')
|
|
||||||
|
|
||||||
interface ElementBindings {
|
|
||||||
type?: string;
|
|
||||||
rel?: string;
|
|
||||||
target?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementBindings = ref({})
|
export interface BaseButtonEmits {
|
||||||
|
(e: 'click', payload: MouseEvent): void
|
||||||
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
|
const {
|
||||||
// we also set a predefined value for the attribute rel, but make it possible to overwrite this by the user.
|
type = BASE_BUTTON_TYPES_MAP.BUTTON,
|
||||||
if ('href' in attrs) {
|
disabled = false,
|
||||||
nodeName = 'a'
|
} = defineProps<BaseButtonProps>()
|
||||||
bindings = {
|
|
||||||
rel: 'noreferrer noopener nofollow',
|
|
||||||
target: '_blank',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentNodeName.value = nodeName
|
const emit = defineEmits<BaseButtonEmits>()
|
||||||
elementBindings.value = {
|
|
||||||
...bindings,
|
|
||||||
...attrs,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const isButton = computed(() => componentNodeName.value === 'button')
|
const button = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
const button = ref()
|
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
button.value.focus()
|
unrefElement(button)?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user