CommonLink.vue 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. <!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, toRef } from 'vue'
  4. import { useLink } from 'vue-router'
  5. import stopEvent from '@shared/utils/events'
  6. import type { Link } from '@shared/types/router'
  7. export interface Props {
  8. link: Link
  9. isExternal?: boolean
  10. isRoute?: boolean
  11. disabled?: boolean
  12. rel?: string
  13. target?: string
  14. openInNewTab?: boolean
  15. replace?: boolean
  16. activeClass?: string
  17. exactActiveClass?: string
  18. }
  19. const props = withDefaults(defineProps<Props>(), {
  20. isExternal: false,
  21. isRoute: false,
  22. replace: false,
  23. append: false,
  24. openInNewTab: false,
  25. disabled: false,
  26. activeClass: 'router-link-active',
  27. exactActiveClass: 'router-link-exact-active',
  28. })
  29. const emit = defineEmits<{
  30. (e: 'click', event: MouseEvent): void
  31. }>()
  32. const target = computed(() => {
  33. if (props.target) return props.target
  34. if (props.openInNewTab) return '_blank'
  35. return null
  36. })
  37. // TODO: Correct styling is currently missing.
  38. const linkClass = computed(() => {
  39. if (props.disabled) {
  40. return 'pointer-events-none'
  41. }
  42. return ''
  43. })
  44. const { href, route, navigate, isActive, isExactActive } = useLink({
  45. to: toRef(props, 'link'),
  46. replace: toRef(props, 'replace'),
  47. })
  48. const isInternalLink = computed(() => {
  49. if (props.isExternal) return false
  50. if (props.isRoute) return true
  51. // zammad desktop urls
  52. if (route.value.fullPath.startsWith('/#')) return false
  53. return route.value.matched.length > 0 && route.value.name !== 'Error'
  54. })
  55. const onClick = (event: MouseEvent) => {
  56. if (props.disabled) {
  57. stopEvent(event, { immediatePropagation: true })
  58. return
  59. }
  60. emit('click', event)
  61. if (isInternalLink.value) {
  62. navigate(event)
  63. }
  64. // Stop the scroll-to-top behavior or navigation on regular links when href is just '#'.
  65. if (!isInternalLink.value && props.link === '#') {
  66. stopEvent(event, { propagation: false })
  67. }
  68. }
  69. </script>
  70. <template>
  71. <a
  72. data-test-id="common-link"
  73. :href="isInternalLink ? href : (link as string)"
  74. :target="target"
  75. :rel="rel"
  76. :class="[
  77. linkClass,
  78. {
  79. [activeClass]: isActive,
  80. [exactActiveClass]: isExactActive,
  81. },
  82. ]"
  83. @click="onClick"
  84. >
  85. <slot></slot>
  86. </a>
  87. </template>