|
<script setup lang="ts"> |
|
import { PropType, computed, ref, useSlots } from 'vue'; |
|
|
|
import { |
|
TpButtonType, |
|
TpButtonSize, |
|
TpButtonVariant, |
|
TpButtonTag, |
|
TpButtonColor, |
|
TP_BUTTON_EVENTS, |
|
} from './tp-button.constants'; |
|
|
|
const emit = defineEmits<{ |
|
(event: TP_BUTTON_EVENTS.CLICK, payload: Event): void; |
|
}>(); |
|
|
|
const slots = useSlots(); |
|
|
|
const props = defineProps({ |
|
disabled: { type: Boolean, default: false }, |
|
fullWidth: { type: Boolean, default: false }, |
|
iconOnly: { type: Boolean, default: false }, |
|
pill: { type: Boolean, default: false }, |
|
bold: { type: Boolean, default: false }, |
|
type: { type: String as PropType<TpButtonType>, default: 'button' as TpButtonType }, |
|
size: { type: String as PropType<TpButtonSize>, default: 'medium' as TpButtonSize }, |
|
variant: { type: String as PropType<TpButtonVariant>, default: 'default' as TpButtonVariant }, |
|
color: { type: String as PropType<TpButtonColor>, default: 'default' as TpButtonColor }, |
|
canMultipleTriggerDuringKeyPress: { type: Boolean, default: false }, |
|
tag: { type: String as PropType<TpButtonTag>, default: 'button' as TpButtonTag }, |
|
}); |
|
|
|
const isKeyPressed = ref<boolean>(false); |
|
|
|
const rootClasses = computed<Record<string, boolean>>(() => { |
|
return { |
|
'tp-button_full-width': props.fullWidth, |
|
'tp-button_icon-only': props.iconOnly, |
|
'tp-button_pill': props.pill, |
|
'tp-button_text_bold': props.bold, |
|
'tp-button_disabled': props.disabled, |
|
[`tp-button_size_${props.size}`]: props.size !== 'custom', |
|
[`tp-button_variant_${props.variant}`]: props.variant !== 'custom', |
|
[`tp-button_color_${props.color}`]: props.color !== 'custom', |
|
}; |
|
}); |
|
|
|
const isButtonTag = computed<boolean>(() => { |
|
return props.tag === 'button'; |
|
}); |
|
|
|
function handleClick(event: Event): void { |
|
if (props.disabled) { |
|
event.preventDefault(); // Prevents the default behavior when a root HTML tag is "a". |
|
} else { |
|
emit(TP_BUTTON_EVENTS.CLICK, event); |
|
} |
|
} |
|
|
|
function handleKeyPress(event: KeyboardEvent): void { |
|
if (!props.canMultipleTriggerDuringKeyPress) { |
|
if (isKeyPressed.value) { |
|
event.preventDefault(); |
|
} else { |
|
isKeyPressed.value = true; |
|
} |
|
} |
|
} |
|
|
|
function handleKeyUp(): void { |
|
if (!props.canMultipleTriggerDuringKeyPress) { |
|
isKeyPressed.value = false; |
|
} |
|
} |
|
</script> |
|
|
|
<template> |
|
<component |
|
:is="tag" |
|
class="tp-button" |
|
:class="rootClasses" |
|
:type="isButtonTag ? type : undefined" |
|
:aria-disabled="disabled" |
|
@click="handleClick" |
|
@keypress.space.enter="handleKeyPress" |
|
@keyup.space.enter="handleKeyUp" |
|
@blur="isKeyPressed = false" |
|
> |
|
<span v-if="slots.icon" class="tp-button__icon"><slot name="icon"></slot></span> |
|
<span v-if="slots.default && !iconOnly" class="tp-button__label"><slot></slot></span> |
|
<span v-if="slots.iconEnd" class="tp-button__icon"><slot name="iconEnd"></slot></span> |
|
</component> |
|
</template> |
|
|
|
<style lang="scss"> |
|
$sizeSmall: 24px; |
|
$sizeMedium: 32px; |
|
$sizeLarge: 40px; |
|
|
|
.tp-button { |
|
$root: &; |
|
$variants: ('default', 'outlined', 'pale', 'clear', 'link'); |
|
$colors: ( |
|
'default', |
|
'blue', |
|
'brand-dark', |
|
'brand-light', |
|
'gray', |
|
'green', |
|
'orange', |
|
'purple', |
|
'red', |
|
'yellow' |
|
); |
|
position: relative; |
|
display: inline-flex; |
|
align-items: center; |
|
justify-content: center; |
|
|
|
box-sizing: border-box; |
|
max-width: 100%; |
|
margin: 0; |
|
padding: 0; |
|
color: inherit; |
|
font-family: $defaultFont; |
|
text-decoration: none; |
|
background-color: transparent; |
|
border: 0; |
|
border-radius: 0; |
|
cursor: pointer; |
|
transition-timing-function: $defaultTransitionTimingFunction; |
|
transition-duration: 0.2s; |
|
transition-property: none; |
|
appearance: none; |
|
user-select: none; |
|
|
|
&:focus-visible { |
|
@include outline(2px); |
|
} |
|
|
|
& > * + * { |
|
margin-left: 6px; |
|
} |
|
|
|
&__icon { |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
box-sizing: border-box; |
|
width: 1em; |
|
min-width: 1em; |
|
height: 1em; |
|
fill: currentColor; |
|
|
|
svg { |
|
width: 100%; |
|
height: 100%; |
|
} |
|
} |
|
|
|
&__label { |
|
position: relative; |
|
box-sizing: border-box; |
|
overflow: hidden; |
|
white-space: nowrap; |
|
text-overflow: ellipsis; |
|
} |
|
|
|
&_text_bold { |
|
font-weight: 600; |
|
} |
|
|
|
&_full-width { |
|
width: 100%; |
|
} |
|
|
|
&_size { |
|
&_small { |
|
height: $sizeSmall; |
|
padding-right: 10px; |
|
padding-left: 10px; |
|
font-size: 13px; |
|
line-height: 1.4285714286; |
|
|
|
#{$root}__icon { |
|
font-size: 16px; |
|
} |
|
} |
|
|
|
&_medium { |
|
height: $sizeMedium; |
|
padding-right: 10px; |
|
padding-left: 10px; |
|
font-size: 14px; |
|
line-height: 1.4285714286; |
|
|
|
#{$root}__icon { |
|
font-size: 18px; |
|
} |
|
} |
|
|
|
&_large { |
|
height: $sizeLarge; |
|
padding-right: 16px; |
|
padding-left: 16px; |
|
font-size: 16px; |
|
line-height: 1.5; |
|
|
|
#{$root}__icon { |
|
font-size: 20px; |
|
} |
|
} |
|
} |
|
|
|
&_icon-only { |
|
padding-right: 0; |
|
padding-left: 0; |
|
|
|
&#{$root}_size { |
|
&_small { |
|
width: $sizeSmall; |
|
} |
|
|
|
&_medium { |
|
width: $sizeMedium; |
|
} |
|
|
|
&_large { |
|
width: $sizeLarge; |
|
} |
|
} |
|
} |
|
|
|
&_pill { |
|
&#{$root}_size { |
|
&_small { |
|
border-radius: $sizeSmall; |
|
} |
|
|
|
&_medium { |
|
border-radius: $sizeMedium; |
|
} |
|
|
|
&_large { |
|
border-radius: $sizeLarge; |
|
} |
|
} |
|
} |
|
|
|
&_variant { |
|
&_default, |
|
&_outlined, |
|
&_pale, |
|
&_clear { |
|
letter-spacing: 0.2649152297px; |
|
text-decoration: none; |
|
border-radius: 4px; |
|
transition-property: background, box-shadow; |
|
} |
|
|
|
&_link { |
|
height: auto; |
|
padding-right: 0; |
|
padding-left: 0; |
|
border-radius: 2px; |
|
|
|
&:hover, |
|
&:focus-visible { |
|
text-decoration: underline; |
|
} |
|
} |
|
} |
|
|
|
&_disabled:not(#{$root}_variant_link) { |
|
cursor: not-allowed; |
|
} |
|
|
|
@include use-theme() using ($current-theme) { |
|
$component: get-in($current-theme, 'tp-button'); |
|
|
|
@each $variant in $variants { |
|
&_variant_#{$variant} { |
|
@each $color in $colors { |
|
&#{$root}_color_#{$color} { |
|
$color-tone: get-in($component, '#{$color}-tone'); |
|
$current-variant: get-in($color-tone, $variant); |
|
$state-hover: get-in($current-variant, 'hover'); |
|
|
|
color: get-in($current-variant, 'color'); |
|
background-color: get-in($current-variant, 'bg-color'); |
|
box-shadow: get-in($current-variant, 'box-shadow'); |
|
|
|
&:hover:not(#{$root}_disabled), |
|
&:focus-visible:not(#{$root}_disabled) { |
|
color: get-in($state-hover, 'color'); |
|
background-color: get-in($state-hover, 'bg-color'); |
|
box-shadow: get-in($state-hover, 'box-shadow'); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
</style> |