first commit

This commit is contained in:
sajjadtalkhabi
2026-05-13 01:43:05 +03:30
commit d2a577f254
297 changed files with 36274 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
<template>
<div
class="circle-button-wrap"
@mouseenter="onShow"
@mouseleave="onHide"
@focusin="onShow"
@focusout="onHide"
>
<button
ref="triggerRef"
:type="type"
class="circle-button"
:style="buttonStyle"
:disabled="disabled || loading"
@click="handleClick"
>
<slot name="icon" />
</button>
<Teleport to="body">
<Transition name="tooltip-fade">
<div
v-if="showTooltip && tooltip"
ref="tooltipRef"
class="circle-button__tooltip"
:class="`circle-button__tooltip--${actualPlacement}`"
:style="tooltipStyle"
role="tooltip"
>
{{ tooltip }}
<span
class="circle-button__tooltip-arrow"
:style="arrowStyle"
/>
</div>
</Transition>
</Teleport>
</div>
</template>
<script setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
const props = defineProps({
type: { type: String, default: 'button' },
tooltip: { type: String, default: '' },
tooltipPosition: {
type: String,
default: 'top',
validator: (v) => ['top', 'bottom'].includes(v),
},
bgColor: { type: String, default: '#fff' },
size: { type: String, default: '2.75rem' },
disabled: { type: Boolean, default: false },
loading: { type: Boolean, default: false },
})
const emit = defineEmits(['click'])
const showTooltip = ref(false)
const triggerRef = ref(null)
const tooltipRef = ref(null)
const arrowEl = ref(null)
const tooltipStyle = ref({ position: 'fixed', top: '0', left: '0' })
const arrowStyle = ref({})
const actualPlacement = ref(props.tooltipPosition)
const buttonStyle = computed(() => ({
width: props.size,
height: props.size,
backgroundColor: props.bgColor,
}))
let cleanupAutoUpdate = null
const updatePosition = async () => {
if (!triggerRef.value || !tooltipRef.value) return
arrowEl.value = tooltipRef.value.querySelector('.circle-button__tooltip-arrow')
const middleware = [offset(6), flip(), shift({ padding: 8 })]
if (arrowEl.value) middleware.push(arrow({ element: arrowEl.value }))
const result = await computePosition(triggerRef.value, tooltipRef.value, {
placement: props.tooltipPosition,
strategy: 'fixed',
middleware,
})
tooltipStyle.value = {
position: 'fixed',
top: `${result.y}px`,
left: `${result.x}px`,
}
actualPlacement.value = result.placement
const arrowData = result.middlewareData.arrow
if (arrowEl.value && arrowData) {
const side = result.placement.split('-')[0]
const oppositeSide = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }[side]
arrowStyle.value = {
left: arrowData.x != null ? `${arrowData.x}px` : '',
top: arrowData.y != null ? `${arrowData.y}px` : '',
[oppositeSide]: '-0.25rem',
}
}
}
const startAutoUpdate = () => {
if (!triggerRef.value || !tooltipRef.value) return
cleanupAutoUpdate?.()
cleanupAutoUpdate = autoUpdate(triggerRef.value, tooltipRef.value, updatePosition)
}
const stopAutoUpdate = () => {
cleanupAutoUpdate?.()
cleanupAutoUpdate = null
}
watch(showTooltip, async (open) => {
if (open) {
await nextTick()
startAutoUpdate()
} else {
stopAutoUpdate()
}
})
onBeforeUnmount(stopAutoUpdate)
const onShow = () => {
if (!props.tooltip) return
showTooltip.value = true
}
const onHide = () => {
showTooltip.value = false
}
const handleClick = (event) => {
if (props.disabled || props.loading) {
event.preventDefault()
return
}
emit('click', event)
}
</script>
<style lang="scss" scoped>
.circle-button-wrap {
position: relative;
display: inline-block;
}
.circle-button {
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 9999px;
cursor: pointer;
padding: 0;
z-index: 20;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
</style>
<style lang="scss">
.circle-button__tooltip {
z-index: 1000;
white-space: nowrap;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
color: #fff;
background-color: #000;
border-radius: 0.375rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 5%);
pointer-events: none;
}
.circle-button__tooltip-arrow {
position: absolute;
width: 0.5rem;
height: 0.5rem;
background-color: #000;
transform: rotate(45deg);
}
.tooltip-fade-enter-active,
.tooltip-fade-leave-active {
transition: opacity 0.15s ease;
}
.tooltip-fade-enter-from,
.tooltip-fade-leave-to {
opacity: 0;
}
</style>