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,127 @@
<template>
<div
:class="[
cn.b(),
cn.is('loading', loading),
cn.is('checked', checked),
cn.is('focused', focused),
cn.is('disabled', disabled),
]"
>
<div
:class="cn.e('control')"
@click="switchValue"
>
<input
type="checkbox"
:class="cn.e('input')"
:disabled="disabled"
:checked="checked"
:value="trueValue"
@focus="focused = true"
@blur="focused = false"
@change="handleChange"
>
<div :class="cn.e('action')">
<UiSpinner
v-if="loading && !disabled"
:class="cn.e('spinner')"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, toRef } from 'vue'
import { isPromise } from '@vue/shared'
import { useField } from 'vee-validate'
import useClassname from '../../composables/use-classname'
import UiSpinner from '../spinner/index.vue'
export interface Props {
id: string
disabled?: boolean
modelValue?: boolean | string | number
trueValue?: boolean | string | number
falseValue?: boolean | string | number
dummy?: boolean
loading?: boolean
beforeChange?: () => Promise<boolean> | boolean
}
defineOptions({
name: 'UiSwitch',
})
const props = withDefaults(defineProps<Props>(), {
disabled: false,
trueValue: true,
falseValue: false,
dummy: false,
modelValue: undefined,
loading: false,
})
const emit = defineEmits<{
'update:modelValue': [state: boolean | string | number]
change: [state: boolean | string | number]
focus: []
blur: []
}>()
const cn = useClassname('ui-switch')
const { checked, handleChange: fieldHandleChange } = useField(
toRef(props, 'id'),
null,
{
type: 'checkbox',
initialValue: props.modelValue ?? props.falseValue,
checkedValue: props.trueValue,
uncheckedValue: props.falseValue,
syncVModel: true,
},
)
const focused = ref(false)
const handleChange = computed<(e: Event) => void>(() => {
return props.dummy ? handleChangeDummy : fieldHandleChange
})
function handleChangeDummy() {
emit('change', checked.value ? props.falseValue : props.trueValue)
}
async function switchValue(e: Event) {
if (props.disabled || props.loading)
return
const { beforeChange } = props
if (!beforeChange) {
handleChange.value(e)
return
}
const shouldChange = beforeChange()
if (isPromise(shouldChange)) {
try {
const result = await shouldChange
result && handleChange.value(e)
}
catch {}
}
else if (shouldChange) {
handleChange.value(e)
}
}
</script>
<style lang="scss">
@use 'styles';
</style>

View File

@@ -0,0 +1,93 @@
@use '../../styles/mixins' as *;
@use '../../styles/variables' as *;
.ui-switch {
$self: &;
display: inline-flex;
&__control {
position: relative;
border-radius: 5px;
background: var(--switch-background);
width: 28px;
height: 10px;
cursor: pointer;
transition: background 0.2s ease;
outline: none;
#{$self}.is-loading & {
cursor: wait;
}
#{$self}.is-disabled & {
cursor: not-allowed;
background-color: var(--switch-disabled-background);
}
}
&__input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
pointer-events: none;
}
&__action {
--action-size: var(--switch-action-size, 16px);
position: absolute;
top: -3px;
left: 0;
width: var(--action-size);
height: var(--action-size);
background: var(--switch-off-action-background);
border-radius: 50%;
transition: .2s ease;
transition-property: background-color, left;
#{$self}.is-focused &,
#{$self}__control:hover & {
background: var(--switch-off-action-hover-background);
}
#{$self}.is-checked & {
background: var(--switch-on-action-background);
left: calc(100% - var(--action-size));
}
#{$self}:not(.is-disabled).is-checked.is-focused &,
#{$self}:not(.is-disabled).is-checked #{$self}__control:hover & {
background-color: var(--switch-on-action-hover-background);
}
#{$self}.is-disabled & {
background-color: var(--switch-off-action-disabled-background);
}
#{$self}.is-disabled.is-checked & {
background-color: var(--switch-on-action-disabled-background);
}
}
&__spinner {
--spinner-size: 12px;
--spinner-color: #{$clr-white};
margin: 2px;
}
/* prettier-ignore */
@include element-variant('switch', '', (
'background': $clr-grey-300,
'disabled-background': $clr-grey-200,
'off-action-background': $clr-grey-400,
'off-action-hover-background': $clr-grey-500,
'off-action-disabled-background': $clr-grey-300,
'on-action-background': $clr-cyan-500,
'on-action-hover-background': $clr-cyan-400,
'on-action-disabled-background': $clr-cyan-300
));
}