CommonButton.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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 'secondary':
  70. default:
  71. return ['bg-transparent', 'hover:bg-transparent', 'text-blue-800']
  72. }
  73. })
  74. const sizeClasses = computed(() => {
  75. switch (props.size) {
  76. case 'large':
  77. return ['btn-lg', 'text-base']
  78. case 'medium':
  79. return ['btn-md', 'text-sm']
  80. case 'small':
  81. default:
  82. return ['btn-sm', 'text-xs']
  83. }
  84. })
  85. const paddingClasses = computed(() => {
  86. if (props.icon) return ['p-1']
  87. switch (props.size) {
  88. case 'large':
  89. return ['px-4', 'py-2.5']
  90. case 'medium':
  91. return ['px-3', 'py-2']
  92. case 'small':
  93. default:
  94. return ['px-2.5', 'py-1.5']
  95. }
  96. })
  97. const disabledClasses = computed(() => {
  98. if (!props.disabled) return []
  99. return ['opacity-30', 'pointer-events-none']
  100. })
  101. const borderRadiusClass = computed(() => {
  102. switch (props.size) {
  103. case 'large':
  104. if (props.icon) return 'rounded-lg'
  105. return 'rounded-xl'
  106. case 'medium':
  107. return 'rounded-lg'
  108. case 'small':
  109. default:
  110. return 'rounded-md'
  111. }
  112. })
  113. const iconSizeClass = computed(() => {
  114. switch (props.size) {
  115. case 'large':
  116. return 'small'
  117. case 'medium':
  118. return 'tiny'
  119. case 'small':
  120. default:
  121. return 'xs'
  122. }
  123. })
  124. </script>
  125. <template>
  126. <button
  127. class="-:inline-flex h-min min-h-min flex-shrink-0 flex-nowrap items-center justify-center gap-x-1 border-0 font-normal shadow-none transition duration-200 hover:outline hover:outline-1 hover:outline-offset-1 hover:outline-blue-600 focus:outline-none focus:outline-0 focus:outline-offset-0 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%] dark:hover:outline-blue-900"
  128. :class="[
  129. ...variantClasses,
  130. ...sizeClasses,
  131. ...paddingClasses,
  132. ...disabledClasses,
  133. borderRadiusClass,
  134. {
  135. 'w-full': block,
  136. 'w-min': !block,
  137. },
  138. ]"
  139. :type="type"
  140. :form="form"
  141. :aria-disabled="disabled ? 'true' : undefined"
  142. >
  143. <CommonIcon
  144. v-if="prefixIcon"
  145. class="shrink-0"
  146. decorative
  147. :size="iconSizeClass"
  148. :name="prefixIcon"
  149. />
  150. <CommonIcon
  151. v-if="icon"
  152. class="shrink-0"
  153. decorative
  154. :size="iconSizeClass"
  155. :name="icon"
  156. />
  157. <span v-else class="truncate">
  158. <slot>{{ $t(startCase(variant)) }}</slot>
  159. </span>
  160. <CommonIcon
  161. v-if="suffixIcon"
  162. class="shrink-0"
  163. decorative
  164. :size="iconSizeClass"
  165. :name="suffixIcon"
  166. />
  167. </button>
  168. </template>