This commit is contained in:
Nadar
2026-03-17 13:24:22 +03:00
commit 82e5ac9d81
554 changed files with 29637 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
<template>
<Component
:is="as"
:class="[
cn.b(),
cn.m(type),
cn.m(`${type}_${color}`),
cn.m(size !== 'medium' ? size : ''),
cn.is('one-icon-only', oneIconOnly),
cn.has('icon', hasIcon),
cn.is('disabled', disabled),
cn.is('hover', state === 'hover'),
cn.is('active', state === 'active'),
]"
:disabled="disabled"
v-bind="attributes"
@click="handleClick"
>
<span
v-if="hasLeftIcon && displayIcon"
:class="[cn.e('icon'), cn.em('icon', 'left')]"
>
<slot name="left-icon">
<Component :is="resolveComponent(`ui-icon-${leftIcon}`)" />
</slot>
</span>
<span
v-if="!iconOnly"
:class="[cn.e('content')]"
>
<slot />
</span>
<span
v-if="hasRightIcon && displayIcon"
:class="[cn.e('icon'), cn.em('icon', 'right')]"
>
<slot name="right-icon">
<Component :is="resolveComponent(`ui-icon-${rightIcon}`)" />
</slot>
</span>
<span
v-if="loading && !disabled"
:class="[cn.e('loader')]"
>
<slot name="loader">
<UiSpinner
:delay="typeof loading === 'number' ? loading : undefined"
@done="$emit('update:loading', false)"
/>
</slot>
</span>
</Component>
</template>
<script setup lang="ts">
import type { ButtonHTMLAttributes } from 'vue'
import type { UiIcon } from '#build/types/ui/icons'
export interface Props {
type?: 'filled' | 'outlined' | 'ghost' | 'link'
color?: 'primary' | 'secondary'
size?: 'small' | 'medium' | 'large'
nativeType?: ButtonHTMLAttributes['type']
icon?: UiIcon
leftIcon?: UiIcon
rightIcon?: UiIcon
disabled?: boolean
loading?: boolean | number
state?: 'hover' | 'active'
}
defineOptions({
name: 'UiButton',
})
const props = withDefaults(defineProps<Props>(), {
type: 'filled',
color: 'primary',
size: 'medium',
nativeType: 'button',
disabled: false,
loading: false,
})
const emit = defineEmits<{
click: [e: Event]
'update:loading': []
}>()
const slots = useSlots()
const attrs = useAttrs()
const linkComponent = useGlobalConfig('LINK_COMPONENT', 'a')
const cn = useClassname('ui-button')
const as = computed(() => (attrs.href ? linkComponent.value : 'button'))
const attributes = computed(() => {
if (as.value === 'button') {
return {
type: props.nativeType,
...attrs,
}
}
return attrs
})
const leftIcon = computed(() => props.leftIcon ?? props.icon)
const hasLeftIcon = computed(() => !!leftIcon.value || !!slots.leftIcon)
const hasRightIcon = computed(() => !!props.rightIcon || !!slots.rightIcon)
const hasIcon = computed(() => hasLeftIcon.value || hasRightIcon.value)
const oneIconOnly = computed(
() =>
((hasLeftIcon.value && !hasRightIcon.value)
|| (!hasLeftIcon.value && hasRightIcon.value))
&& !slots.default,
)
const iconOnly = computed(
() => (hasIcon.value || props.loading) && !slots.default,
)
const displayIcon = computed(
() => hasIcon.value && ((props.loading && props.disabled) || !props.loading),
)
function handleClick(event: Event) {
if (props.disabled || props.loading) {
event.stopPropagation()
event.preventDefault()
}
else {
emit('click', event)
}
}
</script>
<style lang="scss">
@use 'styles';
</style>

View File

@@ -0,0 +1,240 @@
@use "sass:list";
@use '../../styles/mixins' as *;
@use '../../styles/variables' as *;
$button-types: 'filled', 'outlined', 'ghost', 'link';
$button-colors: 'primary', 'secondary';
/* prettier-ignore */
$button-variants: [
[
'filled',
'primary',
(
'color': $clr-white,
'background': $clr-cyan-500,
'hover-background': $clr-cyan-400,
'active-background': $clr-cyan-600,
'disabled-background': $clr-cyan-200,
'disabled-color': $clr-cyan-300
)
],
[
'filled',
'secondary',
(
'color': $clr-cyan-500,
'background': $clr-white,
'hover-background': $clr-grey-200,
'active-background': $clr-grey-300,
'disabled-background': $clr-grey-100,
'disabled-color': $clr-grey-400
)
],
[
'outlined',
'primary',
(
'color': $clr-cyan-500,
'border-color': $clr-cyan-500,
'hover-color': $clr-white,
'hover-background': $clr-cyan-500,
'active-color': $clr-white,
'active-background': $clr-cyan-600,
'disabled-border-color': $clr-cyan-300,
'disabled-color': $clr-cyan-300
)
],
[
'outlined',
'secondary',
(
'color': $clr-grey-500,
'icon-color': $clr-grey-400,
'border-color': $clr-grey-300,
'hover-color': $clr-grey-600,
'hover-background': $clr-grey-200,
'active-color': $clr-cyan-700,
'active-background': $clr-grey-300,
'disabled-border-color': $clr-grey-300,
'disabled-color': $clr-grey-400
)
],
[
'ghost',
'primary',
(
'color': $clr-cyan-400,
'hover-color': $clr-cyan-500,
'hover-background': $clr-grey-200,
'active-color': $clr-cyan-600,
'active-background': $clr-grey-300,
'disabled-color': $clr-cyan-300
)
],
[
'ghost',
'secondary',
(
'color': $clr-grey-500,
'icon-color': $clr-grey-400,
'hover-color': $clr-grey-600,
'hover-background': $clr-grey-200,
'active-color': $clr-cyan-700,
'active-background': $clr-grey-300,
'disabled-color': $clr-grey-400
)
],
[
'link',
'primary',
(
'color': $clr-cyan-500,
'hover-color': $clr-cyan-400,
'active-color': $clr-cyan-600,
'disabled-color': $clr-cyan-300
)
],
[
'link',
'secondary',
(
'color': $clr-grey-500,
'icon-color': $clr-grey-400,
'hover-color': $clr-cyan-500,
'active-color': $clr-cyan-600,
'disabled-color': $clr-grey-400
)
]
];
.ui-button {
$self: &;
@include txt-i-m('button');
--spinner-size: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
position: relative;
cursor: pointer;
outline: none;
padding: 7px 15px;
background: var(--button-background, transparent);
color: var(--button-color, inherit);
border-radius: var(--button-border-radius, 12px);
width: var(--button-width, unset);
height: var(--button-height, 40px);
border: 1px solid var(--button-border-color, transparent);
white-space: nowrap;
transition: 0.2s ease-out;
transition-property: background-color, border-color, color;
&:visited {
color: var(--button-color, inherit);
}
&:hover,
&:focus-visible,
&.is-hover {
--button-icon-color: var(--button-hover-color, var(--button-color));
background: var(--button-hover-background, var(--button-background));
color: var(--button-hover-color, var(--button-color));
border-color: var(--button-hover-border-color, transparent);
}
&:active,
&.is-active {
--button-icon-color: var(--button-active-color, var(--button-color));
background: var(--button-active-background, var(--button-background));
color: var(--button-active-color, var(--button-color));
border-color: var(--button-active-border-color, transparent);
}
&.is-disabled {
--button-icon-color: var(--button-icon-disabled-color, var(--button-color));
background: var(--button-disabled-background, transparent);
color: var(--button-disabled-color);
border-color: var(--button-disabled-border-color, transparent);
cursor: not-allowed;
}
&__icon {
color: var(--button-icon-color, inherit);
line-height: 1;
transition: color .2s ease-out;
#{$self}.is-one-icon-only:not(#{$self}--link) & {
color: inherit;
}
}
&__loader {
cursor: wait;
#{$self}--small & {
--spinner-size: 16px;
}
}
&.is-one-icon-only {
padding: 0;
width: var(--button-width, var(--button-height, 40px));
}
&--small {
@include txt-s-m;
--button-height: 32px;
padding: 7px 11px;
border-radius: 8px;
}
&--large {
@include txt-m-m;
--button-height: 48px;
padding: 11px 23px;
}
@each $variant in $button-variants {
$type: list.nth($variant, 1);
$color: list.nth($variant, 2);
$scheme: list.nth($variant, 3);
&--#{$type}_#{$color} {
@each $property, $value in $scheme {
--button-#{$property}: var(--button-#{$type}-#{$color}-#{$property});
}
}
/* prettier-ignore */
@include element-variant('button', $type + '-' + $color, $scheme);
}
&--link {
&_primary,
&_secondary {
width: auto !important;
height: auto !important;
padding: 0 !important;
border: none !important;
background: none !important;
text-decoration: none;
}
}
&--link#{$self}--small {
@include txt-r-m;
}
}