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,86 @@
<template>
<div
:class="[
cn.b(),
cn.is('timer', !indeterminate && remainingTime > 0),
cn.is('indeterminate', indeterminate),
]"
>
<svg :class="[cn.e('circle-wrapper')]">
<circle
:class="[cn.e('circle')]"
cx="50%"
cy="50%"
r="45%"
/>
<circle
:class="[cn.e('inner')]"
cx="50%"
cy="50%"
r="45%"
/>
</svg>
<div
v-if="showSeconds && remainingTime > 0"
:class="[cn.e('remaining-time')]"
>
{{ remainingTime }}
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import useClassname from '../../composables/use-classname.js'
export interface Props {
delay?: number
showSeconds?: boolean
}
defineOptions({
name: 'UiSpinner',
})
const props = withDefaults(defineProps<Props>(), {
delay: 0,
showSeconds: false,
})
const emit = defineEmits<{
done: []
}>()
const cn = useClassname('ui-spinner')
const remainingTime = ref(0)
const indeterminate = computed(() => props.delay === 0)
function countdown() {
let startTimestamp: number, elapsedTime: number
const step = (timestamp: number) => {
startTimestamp = startTimestamp || timestamp
elapsedTime = timestamp - startTimestamp
remainingTime.value = Math.ceil(props.delay - elapsedTime / 1000)
if (elapsedTime < props.delay * 1000)
window.requestAnimationFrame(step)
else
emit('done')
}
window.requestAnimationFrame(step)
}
onMounted(() => {
if (props.delay > 0)
countdown()
})
</script>
<style lang="scss">
@use 'styles';
</style>

View File

@@ -0,0 +1,93 @@
.ui-spinner {
--length: calc(2 * 3.14 * (var(--spinner-size, 20px) * 0.45));
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: var(--spinner-size, 20px);
height: var(--spinner-size, 20px);
border-radius: var(--spinner-border-radius, 50%);
pointer-events: none;
&__circle-wrapper {
position: relative;
width: 100%;
height: 100%;
transform: rotateZ(-90deg);
}
&__circle,
&__inner {
fill: none;
stroke-width: 10%;
stroke-linecap: round;
}
&__circle {
stroke: var(--spinner-color, currentColor);
opacity: var(--spinner-circle-opacity, 0.5);
}
&__inner {
stroke: var(--spinner-color, currentColor);
stroke-dasharray: var(--length);
stroke-dashoffset: 0;
}
&__remaining-time {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
font-size: 12px;
stroke: var(--c-text-gray);
transition: 0.5s ease;
color: var(--spinner-color, currentColor);
-webkit-background-clip: initial;
background-clip: initial;
-webkit-text-fill-color: initial;
text-align: center;
}
&.is-timer {
.ui-spinner__inner {
animation: timer v-bind('delay + "s"') linear;
}
}
&.is-indeterminate {
animation: indeterminate var(--spinner-speed, 1.5s) linear infinite;
.ui-spinner__inner {
stroke-dashoffset: calc(var(--length) * 0.75);
}
}
}
@keyframes indeterminate {
from {
-ms-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-ms-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-webkit-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes timer {
from {
stroke-dashoffset: var(--length);
}
to {
stroke-dashoffset: 0;
}
}