Instantly share code, notes, and snippets.
Created
November 14, 2023 16:12
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save dmjcomdem/69da098701f4b39d089915bb9c6dc9ec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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