import { makeSizeProps, Sizes } from '@/composables/size' import { makeVariantProps } from '@/composables/variant' import { makeComponentProps } from '@/composables/component' import { makeTagProps } from '@/composables/tag' import { useEventListener } from '@vueuse/core' import { UIcon } from '@/components/UIcon/UIcon' import { USpinner } from '@/components/USpinner/USpinner' import { genericComponent, propsFactory, useRender } from '@/utils' import ColorName from '@/types/colors' import { setColorVolume, changeColorName, getNextColor, getColorVolume, } from '@/utils/useColors' import { computed, ref } from 'vue' import { makeRouterProps, useLink } from '@/composables/router' export const makeUBaseButtonProps = propsFactory( { backgroundColor: { type: String, default: 'primary-600' as ColorName }, borderColor: { type: String, default: 'primary-600' as ColorName }, disabled: { type: Boolean, default: false }, destructive: { type: Boolean, default: false }, prependIcon: String, appendIcon: String, active: { type: Boolean, default: undefined, }, loading: { type: Boolean, default: false, }, ...makeVariantProps(), ...makeSizeProps({ size: Sizes.sm }), ...makeComponentProps(), ...makeTagProps(), ...makeRouterProps(), }, 'UBaseButton' ) export type UBaseButtonSlots = { default: never } export const UBaseButton = genericComponent()({ name: 'UBaseButton', props: makeUBaseButtonProps(), emits: { click: (e: MouseEvent) => true, 'keydown:enter': () => true, }, setup(props, { emit, slots, attrs }) { const link = useLink(props, attrs) const isActive = computed(() => { if (props.active !== undefined) { return props.active } if (link.isLink.value) { return link.isActive?.value } return false }) const isDisabled = computed(() => props.disabled) const handleKeyDown = (event: KeyboardEvent) => { if (isFocused.value && event.key === 'Enter' && !props.disabled) { emit('keydown:enter') link.navigate } } useEventListener('keydown', handleKeyDown) function onClick(e: MouseEvent) { if (isDisabled.value) return emit('click', e) link.navigate?.(e) } const isFocused = ref(false) const isHovered = ref(false) const isActivated = ref(false) const hasPrepend = computed(() => !!props.prependIcon) const hasAppend = computed(() => !!props.appendIcon) const prependIcon = computed(() => { if (props.prependIcon) { return props.prependIcon } return '' }) const appendIcon = computed(() => { if (props.appendIcon) { return props.appendIcon } return '' }) //computed colors to watch destructive mode: const backgroundColor = computed(() => { let backgroundColor = props.backgroundColor if (props.destructive) { backgroundColor = setColorVolume( 'error-100', getColorVolume(backgroundColor as ColorName) ) } return backgroundColor as ColorName }) const borderColor = computed(() => { let borderColor = props.borderColor if (props.destructive) { borderColor = setColorVolume( 'error-100', getColorVolume(borderColor as ColorName) ) } return borderColor as ColorName }) const textColor = computed(() => { let color = props.color if (props.variant === 'outline') { color = props.color === 'white' ? changeColorName(props.borderColor as ColorName, 400) : props.color } else if (props.variant === 'text' || props.variant === 'plain') { color = props.color === 'white' ? changeColorName(props.borderColor as ColorName, 200) : props.color } if (props.destructive) { if (color === 'black' || color === 'white') { return color } color = setColorVolume('error-600', getColorVolume(color as ColorName)) } return color as ColorName }) //computed classes for each variant: const defaultClassesButton = computed( () => `bg-${backgroundColor.value} text-${textColor.value} border-${ backgroundColor.value } hover:bg-${getNextColor( backgroundColor.value )} hover:border-${getNextColor(backgroundColor.value)} active:bg-${ backgroundColor.value } active:shadow-xs-btn active:shadow-${setColorVolume( backgroundColor.value, 200 )} focus-visible:shadow-xs-btn focus-visible:shadow-${setColorVolume( backgroundColor.value, 200 )}` ) const DisabledDefaultClassesButton = computed( () => `bg-${backgroundColor.value} text-${textColor.value} border-${backgroundColor.value} opacity-20 cursor-not-allowed` ) const outlineClassesButton = computed( () => `bg-transparent text-${textColor.value} border-${ borderColor.value } hover:bg-${ textColor.value === 'black' ? 'gray-50' : setColorVolume(textColor.value, 50) } hover:text-${changeColorName( textColor.value, 100 )} active:bg-transparent active:shadow-xs-btn active:shadow-${setColorVolume( borderColor.value, 200 )} focus-visible:bg-transparent focus-visible:shadow-xs-btn focus-visible:shadow-${setColorVolume(borderColor.value, 200)}` ) const DisabledOutlineClassesButton = computed( () => `bg-transparent text-${textColor.value} border-${borderColor.value} opacity-20 cursor-not-allowed` ) const textClassesButton = computed( () => `bg-transparent text-${ textColor.value } border-transparent hover:text-${changeColorName( textColor.value, 100 )} active:text-${textColor.value} focus-visible:text-${textColor.value}` ) const plainClassesButton = computed( () => `bg-transparent text-${ textColor.value } border-transparent hover:text-${changeColorName( textColor.value, 100 )} hover:bg-${ textColor.value === 'black' ? 'gray-50' : setColorVolume(textColor.value, 50) } active:text-${textColor.value} active:bg-transparent focus-visible:text-${textColor.value} focus-visible:bg-transparent` ) const DisabledTextClassesButton = computed( () => `bg-transparent text-${textColor.value} border-transparent opacity-20 cursor-not-allowed` ) useRender(() => { const Tag = link.isLink.value ? 'a' : props.tag const hasSlot = computed(() => !!slots.default?.()[0].children) const classes = computed(() => ({ //general styling // eslint-disable-next-line max-len 'box-border flex items-center justify-center gap-2 h-fit w-full font-semibold rounded-md border select-none focus-visible:outline-none': true, ...(props.disabled === false && { 'cursor-pointer': true, }), //variants ...(props.variant === 'default' && props.disabled === false && { [defaultClassesButton.value]: true, }), ...(props.variant === 'outline' && props.disabled === false && { [outlineClassesButton.value]: true, }), ...(props.variant === 'text' && props.disabled === false && { [textClassesButton.value]: true, }), ...(props.variant === 'plain' && props.disabled === false && { [plainClassesButton.value]: true, }), //sizes [props.variant === 'text' ? '' : 'min-h-sm px-3.5']: props.size === 'sm' && hasSlot.value, [props.variant === 'text' ? '' : 'min-h-md px-4']: props.size === 'md' && hasSlot.value, [props.variant === 'text' ? '' : 'min-h-lg px-4']: props.size === 'lg' && hasSlot.value, [props.variant === 'text' ? '' : 'min-h-xl px-5']: props.size === 'xl' && hasSlot.value, [props.variant === 'text' ? '' : 'min-h-xxl px-7']: props.size === 'xxl' && hasSlot.value, [props.variant === 'text' ? 'max-h-5 px-3.5' : '']: props.size === 'sm' && hasSlot.value, [props.variant === 'text' ? 'max-h-5 px-4' : '']: props.size === 'md' && hasSlot.value, [props.variant === 'text' ? 'max-h-6 px-4' : '']: props.size === 'lg' && hasSlot.value, [props.variant === 'text' ? 'max-h-6 px-5' : '']: props.size === 'xl' && hasSlot.value, [props.variant === 'text' ? 'max-h-7 px-7' : '']: props.size === 'xxl' && hasSlot.value, ['text-text-sm']: props.size === 'sm' || props.size === 'md', ['text-text-md']: props.size === 'lg' || props.size === 'xl', ['text-text-lg']: props.size === 'xxl', //sizes fot buttons with icon ['p-2']: !hasSlot.value && props.size === 'sm', ['p-2.5']: !hasSlot.value && props.size === 'md', ['p-3']: !hasSlot.value && props.size === 'lg', ['p-3.5']: !hasSlot.value && props.size === 'xl', ['p-4']: !hasSlot.value && props.size === 'xxl', //disabled states ...(props.disabled === true && props.variant === 'default' && { [DisabledDefaultClassesButton.value]: true, }), ...(props.disabled === true && props.variant === 'outline' && { [DisabledOutlineClassesButton.value]: true, }), ...(props.disabled === true && (props.variant === 'text' || props.variant === 'plain') && { [DisabledTextClassesButton.value]: true, }), })) return ( (isHovered.value = true)} onMouseleave={() => (isHovered.value = false)} onMousedown={() => (isActivated.value = true)} onMouseup={() => (isActivated.value = false)} onFocus={() => (isFocused.value = true)} onBlur={() => (isFocused.value = false)} > {hasPrepend.value ? ( ) : ( '' )} {slots.default?.()} {hasAppend.value ? ( ) : ( '' )} {props.loading ? : null} ) }) return {} }, }) export type UBaseButton = InstanceType