CommonButton.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { startCase } from 'lodash-es'
  4. import { computed } from 'vue'
  5. import type { ButtonSize, ButtonType, ButtonVariant } from './types.ts'
  6. export interface Props {
  7. variant?: ButtonVariant
  8. type?: ButtonType
  9. disabled?: boolean
  10. block?: boolean
  11. form?: string
  12. size?: ButtonSize
  13. prefixIcon?: string
  14. icon?: string
  15. suffixIcon?: string
  16. }
  17. const props = withDefaults(defineProps<Props>(), {
  18. variant: 'secondary',
  19. type: 'button',
  20. size: 'small',
  21. })
  22. const variantClasses = computed(() => {
  23. switch (props.variant) {
  24. case 'primary':
  25. return ['bg-blue-800', 'hover:bg-blue-800', 'text-white']
  26. case 'tertiary':
  27. return [
  28. 'bg-green-200',
  29. 'hover:bg-green-200',
  30. 'dark:bg-gray-600',
  31. 'dark:hover:bg-gray-600',
  32. 'text-gray-300',
  33. 'dark:text-neutral-400',
  34. ]
  35. case 'submit':
  36. return ['bg-yellow-300', 'hover:bg-yellow-300', 'text-black']
  37. case 'danger':
  38. return [
  39. 'bg-pink-100',
  40. 'hover:bg-pink-100',
  41. 'dark:bg-red-900',
  42. 'dark:hover:bg-red-900',
  43. 'text-red-500',
  44. ]
  45. case 'remove':
  46. return [
  47. 'bg-red-400',
  48. 'hover:bg-red-400',
  49. 'dark:bg-red-600',
  50. 'dark:hover:bg-red-600',
  51. 'text-white',
  52. ]
  53. case 'subtle':
  54. return [
  55. 'bg-blue-600',
  56. 'dark:bg-blue-900',
  57. 'hover:bg-blue-600',
  58. 'dark:hover:bg-blue-900',
  59. 'text-black',
  60. 'dark:text-white',
  61. ]
  62. case 'neutral':
  63. return [
  64. 'bg-transparent',
  65. 'hover:bg-transparent',
  66. 'text-gray-100',
  67. 'dark:text-neutral-400',
  68. ]
  69. case 'none':
  70. return []
  71. case 'secondary':
  72. default:
  73. return ['-:bg-transparent', '-:hover:bg-transparent', 'text-blue-800']
  74. }
  75. })
  76. const sizeClasses = computed(() => {
  77. switch (props.size) {
  78. case 'large':
  79. return ['btn-lg', 'text-base']
  80. case 'medium':
  81. return ['btn-md', 'text-sm']
  82. case 'small':
  83. default:
  84. return ['btn-sm', 'text-xs']
  85. }
  86. })
  87. const paddingClasses = computed(() => {
  88. if (props.icon) return ['p-1']
  89. switch (props.size) {
  90. case 'large':
  91. return ['px-4', 'py-2.5']
  92. case 'medium':
  93. return ['px-3', 'py-2']
  94. case 'small':
  95. default:
  96. return ['px-2.5', 'py-1.5']
  97. }
  98. })
  99. const disabledClasses = computed(() => {
  100. if (!props.disabled) return []
  101. return ['opacity-30', 'pointer-events-none']
  102. })
  103. const borderRadiusClass = computed(() => {
  104. switch (props.size) {
  105. case 'large':
  106. if (props.icon) return 'rounded-lg'
  107. return 'rounded-xl'
  108. case 'medium':
  109. return 'rounded-lg'
  110. case 'small':
  111. default:
  112. return 'rounded-md'
  113. }
  114. })
  115. const iconSizeClass = computed(() => {
  116. switch (props.size) {
  117. case 'large':
  118. return 'small'
  119. case 'medium':
  120. return 'tiny'
  121. case 'small':
  122. default:
  123. return 'xs'
  124. }
  125. })
  126. </script>
  127. <template>
  128. <button
  129. class="-:inline-flex -:focus:outline-none -:focus:outline-0 -:focus:outline-offset-0 -:border-0 h-min min-h-min flex-shrink-0 flex-nowrap items-center justify-center gap-x-1 font-normal shadow-none transition-transform duration-200 hover:outline hover:outline-1 hover:outline-offset-1 hover:outline-blue-600 focus:hover:outline focus:hover:outline-1 focus:hover:outline-offset-1 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-blue-800 focus:active:scale-[95%] focus:active:!outline-blue-800 dark:hover:outline-blue-900"
  130. :class="[
  131. ...variantClasses,
  132. ...sizeClasses,
  133. ...paddingClasses,
  134. ...disabledClasses,
  135. borderRadiusClass,
  136. {
  137. 'w-full': block,
  138. 'w-min': !block,
  139. },
  140. ]"
  141. :type="type"
  142. :form="form"
  143. :aria-disabled="disabled ? 'true' : undefined"
  144. >
  145. <slot name="label">
  146. <CommonIcon
  147. v-if="prefixIcon"
  148. class="pointer-events-none shrink-0"
  149. decorative
  150. :size="iconSizeClass"
  151. :name="prefixIcon"
  152. />
  153. <CommonIcon
  154. v-if="icon"
  155. class="pointer-events-none shrink-0"
  156. decorative
  157. :size="iconSizeClass"
  158. :name="icon"
  159. />
  160. <span v-else class="truncate">
  161. <slot>{{ $t(startCase(variant)) }}</slot>
  162. </span>
  163. <CommonIcon
  164. v-if="suffixIcon"
  165. class="pointer-events-none shrink-0"
  166. decorative
  167. :size="iconSizeClass"
  168. :name="suffixIcon"
  169. />
  170. </slot>
  171. </button>
  172. </template>