Skip to content

Instantly share code, notes, and snippets.

@dmjcomdem
Created November 14, 2023 16:12
Show Gist options
  • Save dmjcomdem/69da098701f4b39d089915bb9c6dc9ec to your computer and use it in GitHub Desktop.
Save dmjcomdem/69da098701f4b39d089915bb9c6dc9ec to your computer and use it in GitHub Desktop.
<template>
<component
v-bind="attrs"
:is="componentTag"
:class="[
'button',
{
[`button--${variant}`]: variant,
[`button--${resolveColor}`]: resolveColor,
'button--small': small,
'button--icon': icon,
'button--full': full
}
]"
@click="onClick"
>
<Icon v-if="icon" :name="icon" :size="iconSize" :class="iconColor" />
<span>
<!-- @slot default slot -->
<slot />
</span>
</component>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import Icon from '@/shared/ui/PIcon/PIcon.vue';
import type { RouteLocationRaw } from 'vue-router';
import { useRouter } from 'vue-router';
import type { IconName } from '@/shared/model/types/Icons';
export type VariantType = 'default' | 'outline' | 'text' | 'text-underline';
export type ColorType = 'initial' | 'primary' | 'success' | 'warning' | 'info' | 'gray' | 'navy-blue' | 'error';
interface Props {
/**
* Вариант отображения
*/
variant?: VariantType;
/**
* Тип заливки и цвета
*/
color?: ColorType;
/**
* Тег элемента
*/
tag?: 'button' | 'a';
/**
* Тип кнопки (работает только с тегом PButton)
*/
type?: 'button' | 'submit' | 'reset';
/**
* Ссылка для перехода для браузерной ссылки
*/
href?: string;
/**
* Ссылка для перехода через Vue Router
*/
to?: RouteLocationRaw;
/**
* Блокировка кнопки по состоянию disabled
*/
disabled?: boolean;
/**
* Иконка для кнопки
*/
icon?: IconName;
/**
* Размер иконки
*/
iconSize?: number | string;
/**
* CSS класс для цвета иконки
*/
iconColor?: string;
/**
* Разместить кнопку на 100% ширины родительского блока
*/
full?: boolean;
/**
* Уменьшенный размер
*/
small?: boolean;
/**
* Значение для возможности переноса текста в одну строку
*/
textWrap?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'default',
color: 'initial',
tag: 'button',
type: 'button',
disabled: false,
icon: undefined,
iconSize: 24,
iconColor: undefined,
href: undefined,
to: undefined,
textWrap: false
});
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void;
}>();
const router = useRouter();
const resolveColor = computed(() => {
if (props.color) {
return props.color;
}
return ['text', 'text-underline'].includes(props.variant) ? 'gray' : 'primary';
});
const textWrapCSSProperty = computed(() => (props.textWrap ? 'normal' : 'nowrap'));
const componentTag = computed<string>(() => {
return props.tag || 'button';
});
const attrs = computed(() => {
let _attrs: Record<string, unknown> = {
disabled: props.disabled
};
if (props.tag === 'button') {
_attrs = {
..._attrs,
type: props.type
};
}
if (props.tag === 'a') {
_attrs = {
..._attrs,
href: props.href
};
}
return _attrs;
});
const onClick = (event: MouseEvent) => {
if (props.disabled) {
return;
}
if (props.to) {
router.push(props.to);
return;
}
emit('click', event);
};
</script>
<style lang="scss">
.button {
position: relative;
display: inline-flex;
justify-content: center;
align-items: center;
line-height: 1;
border-radius: var(--border-radius-8);
font-weight: 700;
// Дополнительный кейс для отображения длинных срок внутри кнопки
white-space: v-bind(textWrapCSSProperty);
cursor: pointer;
height: var(--control-height);
padding-left: 4.5rem;
padding-right: 4.5rem;
background-color: transparent;
text-align: left;
/* Variables */
--accent-color: var(--primary-light);
--accent-color-hover: var(--primary-lighten);
--accent-color-active: var(--primary-dark);
border: 1px solid var(--accent-color);
&:focus,
&:hover {
--accent-color: var(--accent-color-hover);
outline: none;
}
&:active {
--accent-color: var(--accent-color-active);
}
&:disabled,
&[disabled='true'] {
--accent-color: var(--disable);
cursor: not-allowed;
box-shadow: none;
}
> span {
display: inline-flex;
justify-content: center;
align-items: center;
&:empty {
display: none;
}
}
/* PIcon */
&--icon:not(.button--text):not(.button--text-underline) {
padding-left: 5.5rem;
svg {
position: absolute;
left: 1.4rem;
}
}
/* Size full type */
&--full {
width: 100%;
}
/* Size small type */
&--small:not(.button--text):not(.button--text-underline) {
height: var(--control-height-small);
padding-left: 1.7rem;
padding-right: 1.7rem;
font-size: var(--text-size-14);
font-weight: 500;
@at-root .button--icon#{&} {
padding-left: 4.4rem;
}
}
/* Default type */
&--default {
background-color: var(--accent-color);
color: var(--white);
box-shadow: var(--shadow-btn);
}
/* Outline type */
&--outline {
--accent-color: var(--primary-light);
--accent-color-hover: var(--primary-lighten);
--accent-color-active: var(--primary-dark);
background-color: var(--white);
color: var(--accent-color);
box-shadow: var(--shadow-btn);
}
/* Text/Text-underline type */
&--text,
&--text-underline {
--accent-color: var(--text-color-light);
--accent-color-hover: var(--text-color-2);
--accent-color-active: var(--text-color-2);
border: none;
height: auto;
color: var(--accent-color);
font-weight: 500;
padding-left: 0;
padding-right: 0;
font-size: var(--text-size-14);
gap: 0.4rem;
justify-content: flex-start;
svg {
position: static;
left: auto;
}
@at-root .button--text-underline#{&} {
span {
border-bottom: 1px solid currentColor;
}
}
}
/* Fill primary color */
&--primary {
--accent-color: var(--primary-light);
--accent-color-hover: var(--primary-lighten);
--accent-color-active: var(--primary-dark);
}
/* Fill success color */
&--success {
--accent-color: var(--success);
--accent-color-hover: var(--success-light);
--accent-color-active: var(--success-dark);
}
/* Fill warning color */
&--warning {
--accent-color: var(--warning);
--accent-color-hover: var(--warning-light);
--accent-color-active: var(--warning-dark);
}
/* Fill error color */
&--error {
--accent-color: var(--danger);
--accent-color-hover: var(--danger-light);
--accent-color-active: var(--danger-dark);
}
/* Fill error color */
&--info {
--accent-color: var(--info);
--accent-color-hover: var(--info-light);
--accent-color-active: var(--info-dark);
}
/* Fill error color */
&--gray {
--accent-color: var(--text-color-light);
--accent-color-hover: var(--text-color-dark);
--accent-color-active: var(--gray-light);
}
/* Fill navy-blue color */
&--navy-blue {
--accent-color: var(--navy-blue);
--accent-color-hover: var(--navy-blue-dark);
--accent-color-active: var(--navy-blue-light);
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment