Skip to content

Instantly share code, notes, and snippets.

@daniilgri
Created August 23, 2023 14:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daniilgri/788c1b17c5dcf740cf1e83f1d2d48c67 to your computer and use it in GitHub Desktop.
Save daniilgri/788c1b17c5dcf740cf1e83f1d2d48c67 to your computer and use it in GitHub Desktop.
setup() {
function handleClick(): void {
console.log('clicked');
action('clicked');
}
function handleFocus(): void {
console.log('focus');
}
return { ...toRefs(args), handleClick, handleFocus };
},
template: `
<div>
<div>
<TpButton
:disabled="disabled"
:fullWidth="fullWidth"
:iconOnly="iconOnly"
:pill="pill"
:size="size"
:variant="variant"
:color="color"
:type="type"
:bold="bold"
:canMultipleTriggerDuringKeyPress="canMultipleTriggerDuringKeyPress"
:tag="tag"
@click="handleClick"
@focus="handleFocus"
>
<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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment