feat: 다운로드페이지-플랫폼 및 OS타입별 추가
This commit is contained in:
422
layers/components/atoms/Tooltip.vue
Normal file
422
layers/components/atoms/Tooltip.vue
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
type?: 'click' | 'hover'
|
||||||
|
position?:
|
||||||
|
| 'top'
|
||||||
|
| 'bottom'
|
||||||
|
| 'left'
|
||||||
|
| 'right'
|
||||||
|
| 'top-left'
|
||||||
|
| 'top-right'
|
||||||
|
| 'bottom-left'
|
||||||
|
| 'bottom-right'
|
||||||
|
offset?: number
|
||||||
|
backgroundColor?: string
|
||||||
|
textColor?: string
|
||||||
|
textAlign?: string
|
||||||
|
fontSize?: string
|
||||||
|
fontWeight?: string
|
||||||
|
lineHeight?: string
|
||||||
|
letterSpacing?: string
|
||||||
|
arrow?: boolean
|
||||||
|
teleport?: boolean
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
type: 'click',
|
||||||
|
position: 'top',
|
||||||
|
offset: 8,
|
||||||
|
arrow: true,
|
||||||
|
teleport: false,
|
||||||
|
backgroundColor: '#666666',
|
||||||
|
textColor: '#FFFFFF',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '400',
|
||||||
|
lineHeight: '18px',
|
||||||
|
letterSpacing: '-0.24px',
|
||||||
|
})
|
||||||
|
|
||||||
|
const isOpen = defineModel<boolean>('isOpen', { default: false })
|
||||||
|
|
||||||
|
const isLeftAligned = ref(false) // 브라우저 화면 기준으로 툴팁의 왼쪽 가장자리가 화면 밖으로 나가는 경우 좌측 정렬을 합니다.
|
||||||
|
const isRightAligned = ref(false) // 브라우저 화면 기준으로 툴팁의 오른쪽 가장자리가 화면 밖으로 나가는 경우 우측 정렬을 합니다.
|
||||||
|
const tooltipOffsetX = ref(18) // 툴팁 오프셋 X 값
|
||||||
|
const tooltipArrowOffsetX = ref(26) // 툴팁 화살표 오프셋 X 값
|
||||||
|
const tooltipPosition = ref({
|
||||||
|
// 툴팁 위치 값
|
||||||
|
top: null,
|
||||||
|
bottom: null,
|
||||||
|
left: null,
|
||||||
|
right: null,
|
||||||
|
})
|
||||||
|
const tooltipTriggerRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const isPositions = computed(() => {
|
||||||
|
return {
|
||||||
|
isTop:
|
||||||
|
props.position === 'top' && !isLeftAligned.value && !isRightAligned.value,
|
||||||
|
isTopLeft:
|
||||||
|
props.position === 'top-left' ||
|
||||||
|
(props.position === 'top' && isRightAligned.value),
|
||||||
|
isTopRight:
|
||||||
|
props.position === 'top-right' ||
|
||||||
|
(props.position === 'top' && isLeftAligned.value),
|
||||||
|
isBottom:
|
||||||
|
props.position === 'bottom' &&
|
||||||
|
!isLeftAligned.value &&
|
||||||
|
!isRightAligned.value,
|
||||||
|
isBottomLeft:
|
||||||
|
props.position === 'bottom-left' ||
|
||||||
|
(props.position === 'bottom' && isRightAligned.value),
|
||||||
|
isBottomRight:
|
||||||
|
props.position === 'bottom-right' ||
|
||||||
|
(props.position === 'bottom' && isLeftAligned.value),
|
||||||
|
isLeft: props.position === 'left',
|
||||||
|
isRight: props.position === 'right',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 툴팁 초기화 함수입니다.
|
||||||
|
*/
|
||||||
|
const resetTooltipContent = () => {
|
||||||
|
tooltipPosition.value.top = null
|
||||||
|
tooltipPosition.value.bottom = null
|
||||||
|
tooltipPosition.value.left = null
|
||||||
|
tooltipPosition.value.right = null
|
||||||
|
isOpen.value = false
|
||||||
|
isLeftAligned.value = false
|
||||||
|
isRightAligned.value = false
|
||||||
|
tooltipTriggerRef.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 반응형 툴팁 위치 계산 함수입니다.
|
||||||
|
* @param trigger 트리거 요소
|
||||||
|
*/
|
||||||
|
const calculateTooltipPosition = (trigger: HTMLElement) => {
|
||||||
|
const triggerRect = trigger.getBoundingClientRect()
|
||||||
|
const tooltipOffset = props.offset + 4 // 툴팁 오프셋
|
||||||
|
const triggerCenterX = triggerRect.left + triggerRect.width / 2
|
||||||
|
const triggerCenterY = triggerRect.top + triggerRect.height / 2
|
||||||
|
|
||||||
|
const isLefts =
|
||||||
|
isPositions.value.isLeft ||
|
||||||
|
isPositions.value.isTopLeft ||
|
||||||
|
isPositions.value.isBottomLeft ||
|
||||||
|
isLeftAligned.value
|
||||||
|
const isOnlyLeft =
|
||||||
|
isPositions.value.isLeft &&
|
||||||
|
Object.values(isPositions.value).filter(Boolean).length === 1
|
||||||
|
|
||||||
|
const isRights =
|
||||||
|
isPositions.value.isRight ||
|
||||||
|
isPositions.value.isTopRight ||
|
||||||
|
isPositions.value.isBottomRight ||
|
||||||
|
isRightAligned.value
|
||||||
|
const isOnlyRight =
|
||||||
|
isPositions.value.isRight &&
|
||||||
|
Object.values(isPositions.value).filter(Boolean).length === 1
|
||||||
|
|
||||||
|
let topPosition: number | null = null
|
||||||
|
let bottomPosition: number | null = null
|
||||||
|
let leftPosition: number | null = null
|
||||||
|
let rightPosition: number | null = null
|
||||||
|
|
||||||
|
if (isLefts) {
|
||||||
|
if (isOnlyLeft) {
|
||||||
|
leftPosition = triggerRect.left - tooltipOffset
|
||||||
|
} else {
|
||||||
|
leftPosition = triggerRect.left + triggerRect.width + tooltipOffsetX.value
|
||||||
|
}
|
||||||
|
} else if (isRights) {
|
||||||
|
if (isOnlyRight) {
|
||||||
|
leftPosition = triggerRect.left + triggerRect.width + tooltipOffset
|
||||||
|
} else {
|
||||||
|
leftPosition = triggerRect.left - tooltipOffsetX.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
leftPosition = triggerCenterX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isPositions.value.isBottom ||
|
||||||
|
isPositions.value.isBottomLeft ||
|
||||||
|
isPositions.value.isBottomRight
|
||||||
|
) {
|
||||||
|
topPosition = triggerRect.top
|
||||||
|
} else if (
|
||||||
|
isPositions.value.isTop ||
|
||||||
|
isPositions.value.isTopLeft ||
|
||||||
|
isPositions.value.isTopRight
|
||||||
|
) {
|
||||||
|
topPosition = triggerRect.top - tooltipOffset
|
||||||
|
} else {
|
||||||
|
topPosition = triggerCenterY
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltipPosition.value = {
|
||||||
|
top: topPosition,
|
||||||
|
bottom: bottomPosition,
|
||||||
|
left: leftPosition,
|
||||||
|
right: rightPosition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 좌측 정렬이 필요한지 확인 함수입니다.
|
||||||
|
* 브라우저 화면 기준으로 툴팁의 왼쪽 가장자리가 화면 밖으로 나가는 경우 좌측 정렬을 합니다.
|
||||||
|
*/
|
||||||
|
const isValidLeftAligned = (trigger: HTMLElement): boolean => {
|
||||||
|
const tooltipWidth = 280 // 툴팁 최대 너비
|
||||||
|
const triggerRect = trigger.getBoundingClientRect()
|
||||||
|
const tooltipOffset = props.offset + 4 // 툴팁 오프셋
|
||||||
|
|
||||||
|
// trigger의 중앙 위치 계산
|
||||||
|
const triggerCenterX = triggerRect.left + triggerRect.width / 2
|
||||||
|
|
||||||
|
// 중앙 정렬 시 툴팁의 왼쪽 가장자리 위치
|
||||||
|
const tooltipLeftEdge = triggerCenterX - tooltipWidth / 2
|
||||||
|
|
||||||
|
return tooltipLeftEdge < tooltipOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 우측 정렬이 필요한지 확인 함수입니다.
|
||||||
|
* 브라우저 화면 기준으로 툴팁의 오른쪽 가장자리가 화면 밖으로 나가는 경우 우측 정렬을 합니다.
|
||||||
|
*/
|
||||||
|
const isValidRightAligned = (trigger: HTMLElement): boolean => {
|
||||||
|
const tooltipWidth = 280 // 툴팁 최대 너비
|
||||||
|
const triggerRect = trigger.getBoundingClientRect()
|
||||||
|
const windowWidth = window.innerWidth
|
||||||
|
const tooltipOffset = props.offset + 4 // 툴팁 오프셋
|
||||||
|
|
||||||
|
// trigger의 중앙 위치 계산
|
||||||
|
const triggerCenterX = triggerRect.left + triggerRect.width / 2
|
||||||
|
|
||||||
|
// 중앙 정렬 시 툴팁의 오른쪽 가장자리 위치
|
||||||
|
const tooltipRightEdge = triggerCenterX + tooltipWidth / 2
|
||||||
|
|
||||||
|
return tooltipRightEdge > windowWidth - tooltipOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 툴팁 표시 함수입니다.
|
||||||
|
* @param event 이벤트 객체
|
||||||
|
* @param text 툴팁 텍스트
|
||||||
|
*/
|
||||||
|
const handleTooltip = (event: MouseEvent, type: string) => {
|
||||||
|
if (type !== props.type) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const trigger = (event.currentTarget || event.target) as HTMLElement
|
||||||
|
|
||||||
|
isOpen.value = !isOpen.value
|
||||||
|
|
||||||
|
if (isOpen.value) {
|
||||||
|
tooltipTriggerRef.value = trigger
|
||||||
|
isLeftAligned.value = isValidLeftAligned(trigger)
|
||||||
|
isRightAligned.value = isValidRightAligned(trigger)
|
||||||
|
calculateTooltipPosition(trigger)
|
||||||
|
} else {
|
||||||
|
resetTooltipContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 브라우저 리사이즈 시 툴팁 위치 재계산 함수입니다.
|
||||||
|
*/
|
||||||
|
const handleResize = () => {
|
||||||
|
if (isOpen.value && tooltipTriggerRef.value) {
|
||||||
|
isLeftAligned.value = isValidLeftAligned(tooltipTriggerRef.value)
|
||||||
|
isRightAligned.value = isValidRightAligned(tooltipTriggerRef.value)
|
||||||
|
calculateTooltipPosition(tooltipTriggerRef.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="relative z-[100] inline-flex items-center justify-center"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="handleTooltip($event, 'click')"
|
||||||
|
@mouseenter="handleTooltip($event, 'hover')"
|
||||||
|
@mouseleave="handleTooltip($event, 'hover')"
|
||||||
|
>
|
||||||
|
<slot name="trigger" />
|
||||||
|
|
||||||
|
<template v-if="!$slots.trigger">
|
||||||
|
<AtomsIconsStateInfoCircleLine :size="20" color="#7F7F7F" />
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
v-if="!teleport && isOpen"
|
||||||
|
:class="[
|
||||||
|
'absolute z-[10] flex items-center justify-center w-[280px] py-[8px] px-[12px] rounded-[4px]',
|
||||||
|
]"
|
||||||
|
:style="[
|
||||||
|
isPositions.isTop
|
||||||
|
? `top: -${props.offset + 4}px; left: 50%; transform: translate(-50%, -100%)`
|
||||||
|
: isPositions.isBottom
|
||||||
|
? `bottom: -${props.offset + 4}px; left: 50%; transform: translate(-50%, 100%)`
|
||||||
|
: isPositions.isLeft
|
||||||
|
? `top: 50%; left: -${props.offset + 4}px; transform: translate(-100%, -50%)`
|
||||||
|
: isPositions.isRight
|
||||||
|
? `top: 50%; right: -${props.offset + 4}px; transform: translate(100%, -50%)`
|
||||||
|
: isPositions.isTopLeft
|
||||||
|
? `top: -${props.offset + 4}px; right: -${tooltipOffsetX}px; transform: translate(0, -100%)`
|
||||||
|
: isPositions.isTopRight
|
||||||
|
? `top: -${props.offset + 4}px; left: -${tooltipOffsetX}px; transform: translate(0, -100%)`
|
||||||
|
: isPositions.isBottomLeft
|
||||||
|
? `bottom: -${props.offset + 4}px; right: -${tooltipOffsetX}px; transform: translate(0, 100%)`
|
||||||
|
: isPositions.isBottomRight
|
||||||
|
? `bottom: -${props.offset + 4}px; left: -${tooltipOffsetX}px; transform: translate(0, 100%)`
|
||||||
|
: '',
|
||||||
|
`background-color: ${backgroundColor};`,
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="panel" />
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="!$slots.panel"
|
||||||
|
v-dompurify-html="props.content"
|
||||||
|
:class="[
|
||||||
|
`relative flex items-center justify-center w-full text-${props.textAlign} text-[${props.fontSize}] font-[${props.fontWeight}] leading-[${props.lineHeight}] tracking-[${props.letterSpacing}]`,
|
||||||
|
]"
|
||||||
|
:style="[`color: ${props.textColor};`]"
|
||||||
|
></p>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="arrow"
|
||||||
|
class="absolute"
|
||||||
|
:style="[
|
||||||
|
isPositions.isTop
|
||||||
|
? `bottom: -4px; left: 50%; transform: translate(-50%, 0)`
|
||||||
|
: isPositions.isBottom
|
||||||
|
? `top: -4px; left: 50%; transform: translate(-50%, 0) rotate(180deg)`
|
||||||
|
: isPositions.isLeft
|
||||||
|
? `right: -4px; top: 50%; transform: translate(0, -50%) rotate(-90deg)`
|
||||||
|
: isPositions.isRight
|
||||||
|
? `left: -4px; top: 50%; transform: translate(0, -50%) rotate(90deg)`
|
||||||
|
: isPositions.isTopLeft
|
||||||
|
? `bottom: -4px; right: ${tooltipArrowOffsetX}px; transform: translate(0, 0)`
|
||||||
|
: isPositions.isTopRight
|
||||||
|
? `bottom: -4px; left: ${tooltipArrowOffsetX}px; transform: translate(0, 0)`
|
||||||
|
: isPositions.isBottomLeft
|
||||||
|
? `top: -4px; right: ${tooltipArrowOffsetX}px; transform: translate(0, 0) rotate(180deg)`
|
||||||
|
: isPositions.isBottomRight
|
||||||
|
? `top: -4px; left: ${tooltipArrowOffsetX}px; transform: translate(0, 0) rotate(180deg)`
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="6"
|
||||||
|
height="4"
|
||||||
|
viewBox="0 0 6 4"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.2 2.93333L0 0H6L3.8 2.93333C3.4 3.46667 2.6 3.46667 2.2 2.93333Z"
|
||||||
|
:fill="backgroundColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ClientOnly>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div
|
||||||
|
v-if="teleport && isOpen"
|
||||||
|
:class="[
|
||||||
|
'absolute z-[100] flex items-center justify-center w-[280px] py-[8px] px-[12px] rounded-[4px] text-center text-[12px] font-[400] leading-[18px] tracking-[-0.24px] sm:w-auto sm:max-w-[280px]',
|
||||||
|
]"
|
||||||
|
:style="[
|
||||||
|
isPositions.isTop
|
||||||
|
? `transform: translate(-50%, -100%)`
|
||||||
|
: isPositions.isBottom
|
||||||
|
? `transform: translate(-50%, 100%)`
|
||||||
|
: isPositions.isLeft
|
||||||
|
? `transform: translate(0, -50%)`
|
||||||
|
: isPositions.isRight
|
||||||
|
? `transform: translate(0, -50%)`
|
||||||
|
: isPositions.isTopLeft
|
||||||
|
? `transform: translate(-100%, -100%)`
|
||||||
|
: isPositions.isTopRight
|
||||||
|
? `transform: translate(0, -100%)`
|
||||||
|
: isPositions.isBottomLeft
|
||||||
|
? `transform: translate(-100%, 100%)`
|
||||||
|
: isPositions.isBottomRight
|
||||||
|
? `transform: translate(0, 100%)`
|
||||||
|
: '',
|
||||||
|
`top: ${tooltipPosition.top !== null ? `${tooltipPosition.top}px` : 'auto'}; bottom: ${tooltipPosition.bottom !== null ? `${tooltipPosition.bottom}px` : 'auto'}; left: ${tooltipPosition.left !== null ? `${tooltipPosition.left}px` : 'auto'}; right: ${tooltipPosition.right !== null ? `${tooltipPosition.right}px` : 'auto'}; background-color: ${backgroundColor};`,
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="panel" />
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="!$slots.panel"
|
||||||
|
v-dompurify-html="props.content"
|
||||||
|
:class="[
|
||||||
|
`relative flex items-center justify-center w-full text-${props.textAlign} text-[${props.fontSize}] font-[${props.fontWeight}] leading-[${props.lineHeight}] tracking-[${props.letterSpacing}]`,
|
||||||
|
]"
|
||||||
|
:style="[`color: ${props.textColor};`]"
|
||||||
|
></p>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="arrow"
|
||||||
|
class="absolute"
|
||||||
|
:style="[
|
||||||
|
isPositions.isTop
|
||||||
|
? `bottom: -4px; left: 50%; transform: translate(-50%, 0)`
|
||||||
|
: isPositions.isBottom
|
||||||
|
? `top: -4px; left: 50%; transform: translate(-50%, 0) rotate(180deg)`
|
||||||
|
: isPositions.isLeft
|
||||||
|
? `right: -4px; top: 50%; transform: translate(0, -50%) rotate(-90deg)`
|
||||||
|
: isPositions.isRight
|
||||||
|
? `left: -4px; top: 50%; transform: translate(0, -50%) rotate(90deg)`
|
||||||
|
: isPositions.isTopLeft
|
||||||
|
? `bottom: -4px; right: ${tooltipArrowOffsetX}px; transform: translate(0, 0)`
|
||||||
|
: isPositions.isTopRight
|
||||||
|
? `bottom: -4px; left: ${tooltipArrowOffsetX}px; transform: translate(0, 0)`
|
||||||
|
: isPositions.isBottomLeft
|
||||||
|
? `top: -4px; right: ${tooltipArrowOffsetX}px; transform: translate(0, 0) rotate(180deg)`
|
||||||
|
: isPositions.isBottomRight
|
||||||
|
? `top: -4px; left: ${tooltipArrowOffsetX}px; transform: translate(0, 0) rotate(180deg)`
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="6"
|
||||||
|
height="4"
|
||||||
|
viewBox="0 0 6 4"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.2 2.93333L0 0H6L3.8 2.93333C3.4 3.46667 2.6 3.46667 2.2 2.93333Z"
|
||||||
|
:fill="backgroundColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</ClientOnly>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
36
layers/components/atoms/icons/StateInfoCircleLine.vue
Normal file
36
layers/components/atoms/icons/StateInfoCircleLine.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
size?: number | string
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
size: 12,
|
||||||
|
color: 'var(--foreground-gray-500)',
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.75 13.3333C8.28976 13.3333 7.91667 12.9602 7.91667 12.5L7.91667 8.125C7.91667 7.66476 8.28976 7.29167 8.75 7.29167C9.21024 7.29167 9.58333 7.66476 9.58333 8.125V12.5C9.58333 12.9602 9.21024 13.3333 8.75 13.3333Z"
|
||||||
|
:fill="color"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8.75 4.21875C8.14594 4.21875 7.65625 4.70844 7.65625 5.3125C7.65625 5.91656 8.14594 6.40625 8.75 6.40625C9.35406 6.40625 9.84375 5.91656 9.84375 5.3125C9.84375 4.70844 9.35406 4.21875 8.75 4.21875Z"
|
||||||
|
:fill="color"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.5 8.75C17.5 13.5825 13.5825 17.5 8.75 17.5C3.91751 17.5 0 13.5825 0 8.75C0 3.91751 3.91751 0 8.75 0C13.5825 0 17.5 3.91751 17.5 8.75ZM8.75 15.8333C12.662 15.8333 15.8333 12.662 15.8333 8.75C15.8333 4.83798 12.662 1.66667 8.75 1.66667C4.83798 1.66667 1.66667 4.83798 1.66667 8.75C1.66667 12.662 4.83798 15.8333 8.75 15.8333Z"
|
||||||
|
:fill="color"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SplideSlide } from '@splidejs/vue-splide'
|
|
||||||
import { getComponentGroup, getComponentGroupAry } from '#layers/utils/dataUtil'
|
import { getComponentGroup, getComponentGroupAry } from '#layers/utils/dataUtil'
|
||||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||||
import type { Platform } from '#layers/types/components/button'
|
import type { Platform } from '#layers/types/components/button'
|
||||||
@@ -16,6 +15,8 @@ const props = defineProps<Props>()
|
|||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
||||||
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_download.json'
|
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_download.json'
|
||||||
|
const stoveClientDownloadUrl = runtimeConfig.public
|
||||||
|
.stoveClientDownloadUrl as string
|
||||||
|
|
||||||
// Multilingual
|
// Multilingual
|
||||||
const resultGetMultilingual = await useGetMultilingual({
|
const resultGetMultilingual = await useGetMultilingual({
|
||||||
@@ -70,13 +71,6 @@ const mobileSpecArray = ref<Array<string>>(['Android', 'Ios'])
|
|||||||
const mobileOSArray = ref<Array<string>>(['AOS', 'iOS'])
|
const mobileOSArray = ref<Array<string>>(['AOS', 'iOS'])
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const platformList = computed(() => {
|
|
||||||
if (breakpoints.value.isMobile) {
|
|
||||||
return ['MOBILE', 'PC', 'STOVE']
|
|
||||||
} else {
|
|
||||||
return ['PC', 'STOVE', 'MOBILE']
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const driverList = computed(() =>
|
const driverList = computed(() =>
|
||||||
driverArray.value.map(driver => ({
|
driverArray.value.map(driver => ({
|
||||||
id: `DRIVER_${driver}`,
|
id: `DRIVER_${driver}`,
|
||||||
@@ -103,6 +97,7 @@ const mobileSpecList = computed(() =>
|
|||||||
const mobileOSList = computed(() =>
|
const mobileOSList = computed(() =>
|
||||||
mobileOSArray.value.map(os => ({
|
mobileOSArray.value.map(os => ({
|
||||||
id: `MO_OS_${os}`,
|
id: `MO_OS_${os}`,
|
||||||
|
osType: tm(`Download_${os}_Type`),
|
||||||
osCode: os,
|
osCode: os,
|
||||||
osText: tm(`Download_${os}_OS`),
|
osText: tm(`Download_${os}_OS`),
|
||||||
platformCode: tm(`Download_${os}_Platform`),
|
platformCode: tm(`Download_${os}_Platform`),
|
||||||
@@ -141,116 +136,164 @@ const handleMoveFocus = (target: 'pc' | 'mobile') => {
|
|||||||
<section class="section-static">
|
<section class="section-static">
|
||||||
<WidgetsFixSubTitle :title="tm('Download_Section_Platform_Title')" />
|
<WidgetsFixSubTitle :title="tm('Download_Section_Platform_Title')" />
|
||||||
|
|
||||||
<BlocksSlideDefault
|
<div
|
||||||
:per-page="platformList.length"
|
class="relative flex flex-col-reverse sm:flex-row-reverse items-stretch justify-center gap-[20px] w-full md:flex-row"
|
||||||
:gap="20"
|
|
||||||
:arrows="false"
|
|
||||||
:pagination="false"
|
|
||||||
:drag="false"
|
|
||||||
:breakpoints="{
|
|
||||||
1023: {
|
|
||||||
perPage: 'auto',
|
|
||||||
gap: 12,
|
|
||||||
focus: 0,
|
|
||||||
drag: true,
|
|
||||||
padding: { left: 0, right: 0 },
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
class="min-w-[320px] w-[100vw] px-[20px] ml-[-20px] sm:px-[40px] sm:ml-[-40px] md:w-full md:px-0 md:ml-0"
|
|
||||||
>
|
|
||||||
<SplideSlide
|
|
||||||
v-for="platform in platformList"
|
|
||||||
:key="platform"
|
|
||||||
class="flex flex-col items-center justify-between shrink-0 whitespace-normal w-[295px] h-[280px] bg-[#FFFFFF] p-[20px] rounded-[12px] text-left md:w-[calc((100%-40px)/3)] md:h-[314px] md:p-[24px] md:rounded-[16px] lg:w-[420px] lg:h-[340px] lg:p-[32px]"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-start justify-start gap-[8px] w-full md:gap-[12px]"
|
v-if="gameData?.platform_type !== '2'"
|
||||||
|
class="relative flex flex-1 flex-col items-start justify-start h-[270px] py-[20px] rounded-[12px] bg-white sm:h-auto md:flex-[2] md:pt-[24px] md:pb-0 lg:pt-[32px]"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="relative flex flex-col items-start justify-start w-full max-w-[800px] h-full px-[20px] mx-auto md:h-auto md:px-[28px] lg:px-[40px]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="!breakpoints.isMobile"
|
||||||
|
class="absolute top-0 right-[28px] w-[212px] h-[212px] lg:right-[40px]"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="
|
||||||
|
getImageHost('/images/common/img_desktop.png', {
|
||||||
|
imageType: 'common',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:alt="tm('Download_Box_PC_Title')"
|
||||||
|
loading="lazy"
|
||||||
|
draggable="false"
|
||||||
|
class="w-full h-full object-contain"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
<h4
|
<h4
|
||||||
class="relative flex justify-left items-center w-full text-left text-[#1F1F1F] text-[18px] font-bold leading-[26px] tracking-[-0.54px] md:text-[24px] md:leading-[34px] md:tracking-[0.72px]"
|
class="relative flex justify-left items-center w-full gap-[4px] text-left text-[#1F1F1F] text-[18px] font-bold leading-[26px] tracking-[-0.54px] md:text-[24px] md:leading-[34px] md:tracking-[0.72px]"
|
||||||
>
|
>
|
||||||
<span>{{ tm(`Download_Box_${platform}_Title`) }}</span>
|
<span>{{ tm('Download_Box_PC_Title') }}</span>
|
||||||
|
|
||||||
|
<AtomsTooltip
|
||||||
|
position="top"
|
||||||
|
:offset="8"
|
||||||
|
background-color="#666666"
|
||||||
|
text-color="#FFFFFF"
|
||||||
|
:arrow="true"
|
||||||
|
:content="tm('Download_Box_PC_Tooltip')"
|
||||||
|
/>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p
|
<template
|
||||||
v-if="
|
v-for="(description, dIndex) in tm(
|
||||||
tm(`Download_Box_${platform}_Description_List`).length === 1
|
'Download_Box_PC_Description_List'
|
||||||
"
|
|
||||||
v-dompurify-html="tm(`Download_Box_${platform}_Description01`)"
|
|
||||||
class="text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px] md:text-[15px] md:tracking-[-0.45px]"
|
|
||||||
></p>
|
|
||||||
<ul
|
|
||||||
v-else-if="
|
|
||||||
tm(`Download_Box_${platform}_Description_List`).length > 1
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-for="description in tm(
|
|
||||||
`Download_Box_${platform}_Description_List`
|
|
||||||
)"
|
)"
|
||||||
:key="description"
|
:key="dIndex"
|
||||||
v-dompurify-html="tm(description)"
|
>
|
||||||
class="relative pl-[22px] before:content-[''] before:absolute before:top-[10px] before:left-[9px] before:w-[3px] before:h-[3px] before:rounded-full before:bg-[#999999] text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px] md:text-[15px] md:tracking-[-0.45px]"
|
<p
|
||||||
></li>
|
class="relative flex items-center justify-start w-full mt-[12px] text-left text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px] md:text-[15px] md:tracking-[-0.45px]"
|
||||||
</ul>
|
v-dompurify-html="tm(description as string)"
|
||||||
|
></p>
|
||||||
|
</template>
|
||||||
|
|
||||||
<AtomsButton
|
<AtomsButton
|
||||||
v-if="platform !== 'STOVE'"
|
|
||||||
type="action"
|
type="action"
|
||||||
button-size="size-small"
|
button-size="size-small"
|
||||||
background-color="transparent"
|
background-color="transparent"
|
||||||
text-color="#1F1F1F"
|
text-color="#1F1F1F"
|
||||||
class="relative w-auto h-auto px-0 text-[16px] font-[500] leading-[24px] tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
class="relative w-auto h-auto px-0 mt-[12px] text-[16px] font-[500] leading-[24px] tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20"
|
||||||
@click="
|
@click="handleMoveFocus('pc')"
|
||||||
handleMoveFocus(platform.toLowerCase() as 'pc' | 'mobile')
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span>{{ tm(`Download_Box_${platform}_SpecCheck`) }}</span>
|
<span>{{ tm('Download_Box_PC_SpecCheck') }}</span>
|
||||||
<AtomsIconsLongArrowRightLine
|
<AtomsIconsLongArrowRightLine
|
||||||
:size="20"
|
:size="20"
|
||||||
color="#1F1F1F"
|
color="#1F1F1F"
|
||||||
class="relative rotate-90"
|
class="relative rotate-90"
|
||||||
/>
|
/>
|
||||||
</AtomsButton>
|
</AtomsButton>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="breakpoints.isMobile"
|
||||||
|
class="relative flex items-center justify-center w-full h-auto min-h-[48px] py-[14px] px-[40px] mt-[48px] border border-solid border-[rgba(0,0,0,0.1)] rounded-[8px] bg-[#EBEBEB] text-center text-[#999999] text-[14px] font-[500] leading-[20px] tracking-[-0.42px]"
|
||||||
|
>
|
||||||
|
<span>{{ tm('Download_Button_PC_Mobile') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BlocksButtonLauncher
|
||||||
|
v-else-if="breakpoints.isMd || breakpoints.isDesktop"
|
||||||
|
platform="pc"
|
||||||
|
class="!w-full !max-w-[300px] mt-[32px] lg:mt-[48px]"
|
||||||
|
>
|
||||||
|
<span>{{ tm('Download_Button_PC') }}</span>
|
||||||
|
</BlocksButtonLauncher>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-center gap-[8px] w-full md:gap-[12px]"
|
v-if="!breakpoints.isMobile"
|
||||||
|
class="relative flex items-center justify-center w-full p-[25px] mt-[24px] rounded-b-[12px] bg-[#FAFAFA] lg:mt-[32px]"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="relative flex items-center justify-center w-full gap-[8px] text-[#999999] text-[16px] font-[400] leading-[26px] tracking-[-0.48px]"
|
||||||
|
>
|
||||||
|
<span>{{ tm('Download_Text_Not_STOVE_Client') }}</span>
|
||||||
|
<NuxtLink
|
||||||
|
:href="stoveClientDownloadUrl"
|
||||||
|
target="_self"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center justify-start gap-[4px] text-[#3C75FF] text-[16px] font-[500] reading-[24px] tracking-[-0.48px]"
|
||||||
|
>
|
||||||
|
<span>{{ tm('Download_Button_STOVE') }}</span>
|
||||||
|
<AtomsIconsDownloadLine color="#3C75FF" />
|
||||||
|
</NuxtLink>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="gameData?.platform_type !== '1'"
|
||||||
|
class="relative flex flex-1 flex-col items-start justify-start h-[270px] p-[20px] rounded-[12px] bg-white sm:h-auto md:py-[24px] md:px-[28px] lg:pt-[32px] lg:pb-[40px] lg:px-[40px]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="relative flex flex-col items-start justify-start w-full max-w-[800px] mx-auto"
|
||||||
|
>
|
||||||
|
<h4
|
||||||
|
class="relative flex justify-left items-center w-full text-left text-[#1F1F1F] text-[18px] font-bold leading-[26px] tracking-[-0.54px] md:text-[24px] md:leading-[34px] md:tracking-[0.72px]"
|
||||||
|
>
|
||||||
|
<span>{{ tm('Download_Box_MOBILE_Title') }}</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<AtomsButton
|
||||||
|
type="action"
|
||||||
|
button-size="size-small"
|
||||||
|
background-color="transparent"
|
||||||
|
text-color="#1F1F1F"
|
||||||
|
class="relative w-auto h-auto px-0 mt-[8px] mb-[48px] text-[16px] font-[500] leading-[24px] tracking-[-0.48px] before:content-[''] before:absolute before:z-[2] before:top-p before:left-0 before:w-full before:h-full before:bg-[#FFFFFF] before:transition-opacity before:duration-300 before:ease-in-out before:opacity-0 hover:before:opacity-20 md:mt-[12px]"
|
||||||
|
@click="handleMoveFocus('mobile')"
|
||||||
|
>
|
||||||
|
<span>{{ tm('Download_Box_MOBILE_SpecCheck') }}</span>
|
||||||
|
<AtomsIconsLongArrowRightLine
|
||||||
|
:size="20"
|
||||||
|
color="#1F1F1F"
|
||||||
|
class="relative rotate-90"
|
||||||
|
/>
|
||||||
|
</AtomsButton>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="relative flex flex-col items-start justify-start w-full gap-[8px] mt-auto md:gap-[12px]"
|
||||||
>
|
>
|
||||||
<template v-if="platform === 'MOBILE'">
|
|
||||||
<template v-for="os in mobileOSList" :key="os.id">
|
<template v-for="os in mobileOSList" :key="os.id">
|
||||||
<BlocksButtonLauncher
|
<BlocksButtonLauncher
|
||||||
v-if="device.isMobile ? os.isValue : true"
|
v-if="
|
||||||
|
device.isMobile
|
||||||
|
? os.isValue
|
||||||
|
: gameData?.os_type !== '3'
|
||||||
|
? gameData?.os_type === os.osType
|
||||||
|
: true
|
||||||
|
"
|
||||||
:platform="`${os.platformCode as Platform}`"
|
:platform="`${os.platformCode as Platform}`"
|
||||||
class="!w-full"
|
class="!w-full"
|
||||||
>
|
>
|
||||||
<span>{{ os.platformText }}</span>
|
<span>{{ os.platformText }}</span>
|
||||||
</BlocksButtonLauncher>
|
</BlocksButtonLauncher>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<AtomsButton
|
|
||||||
v-if="breakpoints.isMobile"
|
|
||||||
type="action"
|
|
||||||
button-size="size-small"
|
|
||||||
background-color="#EBEBEB"
|
|
||||||
text-color="#999999"
|
|
||||||
:disabled="true"
|
|
||||||
class="w-full px-0 border border-solid border-[rgba(0,0,0,0.1)] cursor-default"
|
|
||||||
>
|
|
||||||
<span>{{ tm(`Download_Button_${platform}_Mobile`) }}</span>
|
|
||||||
</AtomsButton>
|
|
||||||
<BlocksButtonLauncher
|
|
||||||
v-else-if="breakpoints.isMd || breakpoints.isDesktop"
|
|
||||||
:platform="`${platform.toLowerCase() as Platform}`"
|
|
||||||
class="!w-full"
|
|
||||||
>
|
|
||||||
<span>{{ tm(`Download_Button_${platform}`) }}</span>
|
|
||||||
</BlocksButtonLauncher>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</SplideSlide>
|
</div>
|
||||||
</BlocksSlideDefault>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section ref="specPCRef" class="section-static">
|
<section ref="specPCRef" class="section-static">
|
||||||
@@ -426,4 +469,8 @@ table td {
|
|||||||
.splide :deep(.splide__track) {
|
.splide :deep(.splide__track) {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep([data-platform='stove']) .icon-platform {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
BIN
public/images/common/img_desktop.png
Normal file
BIN
public/images/common/img_desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
Reference in New Issue
Block a user