CommonButton.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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.size === 'large' && props.icon) return ['p-2']
  89. if (props.icon) return ['p-1']
  90. switch (props.size) {
  91. case 'large':
  92. return ['px-4', 'py-2.5']
  93. case 'medium':
  94. return ['px-3', 'py-2']
  95. case 'small':
  96. default:
  97. return ['px-2.5', 'py-1.5']
  98. }
  99. })
  100. const disabledClasses = computed(() => {
  101. if (!props.disabled) return []
  102. return ['opacity-30', 'pointer-events-none']
  103. })
  104. const borderRadiusClass = computed(() => {
  105. switch (props.size) {
  106. case 'large':
  107. if (props.icon) return 'rounded-lg'
  108. return 'rounded-xl'
  109. case 'medium':
  110. return 'rounded-lg'
  111. case 'small':
  112. default:
  113. return 'rounded-md'
  114. }
  115. })
  116. const iconSizeClass = computed(() => {
  117. switch (props.size) {
  118. case 'large':
  119. return 'small'
  120. case 'medium':
  121. return 'tiny'
  122. case 'small':
  123. default:
  124. return 'xs'
  125. }
  126. })
  127. </script>
  128. <template>
  129. <button
  130. 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"
  131. :class="[
  132. ...variantClasses,
  133. ...sizeClasses,
  134. ...paddingClasses,
  135. ...disabledClasses,
  136. borderRadiusClass,
  137. {
  138. 'w-full': block,
  139. 'w-min': !block,
  140. },
  141. ]"
  142. :type="type"
  143. :form="form"
  144. :aria-disabled="disabled ? 'true' : undefined"
  145. >
  146. <slot name="label">
  147. <CommonIcon
  148. v-if="prefixIcon"
  149. class="pointer-events-none shrink-0"
  150. decorative
  151. :size="iconSizeClass"
  152. :name="prefixIcon"
  153. />
  154. <CommonIcon
  155. v-if="icon"
  156. class="pointer-events-none block shrink-0"
  157. decorative
  158. :size="iconSizeClass"
  159. :name="icon"
  160. />
  161. <span v-else class="truncate">
  162. <slot>{{ $t(startCase(variant)) }}</slot>
  163. </span>
  164. <CommonIcon
  165. v-if="suffixIcon"
  166. class="pointer-events-none shrink-0"
  167. decorative
  168. :size="iconSizeClass"
  169. :name="suffixIcon"
  170. />
  171. </slot>
  172. </button>
  173. </template>