feat. home 버튼 컴포넌트 추가
This commit is contained in:
26
layers/components/blocks/Button/Home.vue
Normal file
26
layers/components/blocks/Button/Home.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const scrollStore = useScrollStore()
|
||||
|
||||
const { isPassedStoveGnb } = storeToRefs(scrollStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AtomsButtonCircle
|
||||
sr-only="home"
|
||||
type="link"
|
||||
to="/"
|
||||
:class="['btn-home', { 'is-fixed': isPassedStoveGnb }]"
|
||||
>
|
||||
<AtomsIconsHomeFill />
|
||||
</AtomsButtonCircle>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-home {
|
||||
@apply absolute top-3 right-3 mt-[48px] bg-black/20 shadow-[0_1.667px_3.333px_0_rgba(0,0,0,0.06)] backdrop-blur-[12.5px] z-[100]
|
||||
sm:top-5 md:top-6 md:right-8 md:mt-[64px];
|
||||
}
|
||||
.btn-home.is-fixed {
|
||||
@apply fixed;
|
||||
}
|
||||
</style>
|
||||
205
layers/components/blocks/Button/Launcher.vue
Normal file
205
layers/components/blocks/Button/Launcher.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type {
|
||||
DownloadButtonType,
|
||||
ButtonVariant,
|
||||
Platform,
|
||||
} from '#layers/types/components/button'
|
||||
|
||||
interface Props {
|
||||
platform: Platform
|
||||
type?: DownloadButtonType
|
||||
variant?: ButtonVariant
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'default',
|
||||
variant: 'filled',
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { isProcessing, validateLauncher } = useCheckGameStart()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
||||
google_play: 'AtomsIconsLogoGoogle',
|
||||
app_store: 'AtomsIconsLogoApple',
|
||||
pc: 'AtomsIconsLogoWindow',
|
||||
stove: 'AtomsIconsLogoStove',
|
||||
} as const
|
||||
|
||||
const DUP_IMAGE_MAP: Record<Platform, string> = {
|
||||
google_play: '/images/common/btn_logo-google.svg',
|
||||
app_store: '/images/common/btn_logo-app.svg',
|
||||
pc: '/images/common/btn_logo-pc.svg',
|
||||
stove: '/images/common/btn_logo-stove.svg',
|
||||
} as const
|
||||
|
||||
const componentTag = computed(() => {
|
||||
if (props.type !== 'duplication' && props.platform === 'stove') {
|
||||
return 'a'
|
||||
}
|
||||
return 'button'
|
||||
})
|
||||
const isDuplication = computed(() => props.type === 'duplication')
|
||||
const isSingle = computed(() => props.type === 'single')
|
||||
const platformIcon = computed(() => PLATFORM_ICON_MAP[props.platform])
|
||||
|
||||
const inlineStyle = computed<CSSProperties>(() => {
|
||||
const style: CSSProperties = {}
|
||||
|
||||
if (props.backgroundColor) {
|
||||
style.backgroundColor = props.backgroundColor
|
||||
}
|
||||
if (props.textColor) {
|
||||
style.color = props.textColor
|
||||
}
|
||||
if (props.type === 'duplication') {
|
||||
style.backgroundImage = `url(${getImageHost(DUP_IMAGE_MAP[props.platform], { imageType: 'common' })})`
|
||||
}
|
||||
return style
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.platform === 'pc') {
|
||||
validateLauncher()
|
||||
return
|
||||
}
|
||||
if (props.platform === 'stove' && !isDuplication.value) {
|
||||
const stoveClientDownloadUrl = runtimeConfig.public.stoveClientDownloadUrl
|
||||
location.href = stoveClientDownloadUrl
|
||||
return
|
||||
}
|
||||
|
||||
const url = gameData.value?.market_json[props.platform]?.url
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="componentTag"
|
||||
v-bind="$attrs"
|
||||
:class="[
|
||||
'btn-base',
|
||||
props.type,
|
||||
{ 'no-text': isSingle && !$slots.default },
|
||||
]"
|
||||
:data-variant="props.variant"
|
||||
:data-platform="props.platform"
|
||||
:style="inlineStyle"
|
||||
:disabled="disabled || isProcessing"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span class="btn-content">
|
||||
<component
|
||||
:is="platformIcon"
|
||||
v-if="!isDuplication"
|
||||
class="icon-platform"
|
||||
/>
|
||||
<span class="text">
|
||||
<slot />
|
||||
</span>
|
||||
<span v-if="type === 'default'" class="icon-download">
|
||||
<AtomsIconsDownloadLine />
|
||||
</span>
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-base {
|
||||
@apply overflow-hidden inline-flex relative font-medium rounded-lg cursor-pointer
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-white/10 before:rounded-lg
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0 after:rounded-lg;
|
||||
}
|
||||
.btn-base:hover {
|
||||
@apply after:opacity-20;
|
||||
}
|
||||
.btn-content {
|
||||
@apply relative flex items-center w-full text-left z-[1];
|
||||
}
|
||||
.icon-platform {
|
||||
@apply w-5 h-5 flex-shrink-0;
|
||||
}
|
||||
|
||||
.btn-base[data-variant='filled'] {
|
||||
@apply bg-[#383838] text-[#ffffff];
|
||||
}
|
||||
.btn-base[data-variant='outlined'] {
|
||||
@apply bg-white text-[#1F1F1F]
|
||||
before:border-black/10
|
||||
after:hidden;
|
||||
}
|
||||
.btn-base[data-variant='outlined'][data-platform='app_store'] svg,
|
||||
.btn-base[data-variant='outlined'][data-platform='pc'] svg,
|
||||
.btn-base[data-variant='outlined'][data-platform='stove'] svg {
|
||||
@apply fill-[#1F1F1F];
|
||||
}
|
||||
|
||||
/* default */
|
||||
.btn-base.default {
|
||||
@apply w-[296px] py-3.5 px-5
|
||||
md:w-[356px] md:py-4 md:px-6;
|
||||
}
|
||||
.btn-base.default .text {
|
||||
@apply pl-2 pr-4 line-clamp-2 text-[14px]
|
||||
md:text-[16px];
|
||||
}
|
||||
.btn-base.default .icon-download {
|
||||
@apply border-l border-white/10 ml-auto pl-4;
|
||||
}
|
||||
.btn-base.default[data-platform='stove'] {
|
||||
@apply bg-[#FC4420];
|
||||
}
|
||||
.btn-base.default[data-variant='outlined'] .icon-download {
|
||||
@apply border-black/10;
|
||||
}
|
||||
.btn-base.default[data-variant='outlined'] .icon-download svg {
|
||||
@apply fill-[#1F1F1F];
|
||||
}
|
||||
|
||||
/* duplication */
|
||||
.btn-base.duplication {
|
||||
@apply bg-[16px_50%] bg-[length:auto_28px] bg-no-repeat
|
||||
pt-[22px] pl-[47px] pr-[22px] pb-[7px] text-[11px]
|
||||
md:h-[64px] md:pt-[30px] md:pl-[64px] md:pr-[28px] md:pb-[11px] md:text-[12px] md:bg-[20px_50%] md:bg-[length:auto_40px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='google_play'] {
|
||||
@apply min-w-[148px] md:min-w-[194px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='app_store'] {
|
||||
@apply min-w-[132px] md:min-w-[174px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='pc'] {
|
||||
@apply min-w-[113px] md:min-w-[150px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='stove'] {
|
||||
@apply min-w-[113px] md:min-w-[150px];
|
||||
}
|
||||
.btn-base.duplication .text {
|
||||
@apply line-clamp-1;
|
||||
}
|
||||
|
||||
/* single */
|
||||
.btn-base.single {
|
||||
@apply justify-center items-center text-[14px]
|
||||
h-[40px] px-3.5
|
||||
md:h-[48px];
|
||||
}
|
||||
.btn-base.single.no-text {
|
||||
@apply min-w-[40px] px-0 md:min-w-[48px];
|
||||
}
|
||||
.btn-base.single.no-text .icon-platform {
|
||||
@apply mx-auto;
|
||||
}
|
||||
.btn-base.single .icon-platform {
|
||||
@apply mr-1;
|
||||
}
|
||||
</style>
|
||||
26
layers/components/blocks/Button/ScrollTop.vue
Normal file
26
layers/components/blocks/Button/ScrollTop.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
|
||||
const showBtn = computed(() => windowY.value > 0)
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
windowY.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<AtomsButtonCircle
|
||||
v-show="showBtn"
|
||||
class="btn-top"
|
||||
sr-only="top"
|
||||
@click="handleScrollToTop"
|
||||
/>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-top {
|
||||
@apply bg-[image:var(--button-top)] bg-center bg-cover bg-no-repeat;
|
||||
}
|
||||
</style>
|
||||
119
layers/components/blocks/Button/Sns.vue
Normal file
119
layers/components/blocks/Button/Sns.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
const showSnsList = ref(false)
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
const { handleOpenToast } = modalStore
|
||||
|
||||
const snsBackgroundColor = computed(() => {
|
||||
const colorData = gameData.value?.comm_sns_bg_color_json?.display
|
||||
const colorCode = getColorCode({
|
||||
colorName: colorData?.color_name,
|
||||
colorCode: colorData?.color_code,
|
||||
})
|
||||
return colorCode
|
||||
})
|
||||
const snsList = computed(() => {
|
||||
return gameData.value?.sns_json
|
||||
})
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
showSnsList.value = true
|
||||
}
|
||||
|
||||
const handleForceClose = () => {
|
||||
showSnsList.value = false
|
||||
}
|
||||
|
||||
const handleCopy = async () => {
|
||||
if (!import.meta.client) return
|
||||
|
||||
try {
|
||||
const url = window.location.href
|
||||
await navigator.clipboard.writeText(url)
|
||||
handleOpenToast('복사 성공')
|
||||
} catch (error) {
|
||||
console.error('복사 실패:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="Object.keys(snsList).length > 0" class="sns-container">
|
||||
<AtomsButtonCircle
|
||||
class="btn-sns"
|
||||
sr-only="sns"
|
||||
:style="{ backgroundColor: snsBackgroundColor }"
|
||||
@click="handleMouseEnter"
|
||||
>
|
||||
<AtomsIconsShareLine class="icon-share" />
|
||||
</AtomsButtonCircle>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="showSnsList"
|
||||
class="sns-list"
|
||||
:style="{ backgroundColor: snsBackgroundColor }"
|
||||
>
|
||||
<template v-for="(item, key) in snsList" :key="key">
|
||||
<a
|
||||
v-if="item?.url"
|
||||
:href="item?.url"
|
||||
target="_blank"
|
||||
class="sns-item"
|
||||
:style="{
|
||||
backgroundImage: `url(${getImageHost(`/images/common/ic-v2-logo-${key}-fill.png`, { imageType: 'common' })})`,
|
||||
}"
|
||||
>
|
||||
<span class="sr-only">{{ key }}</span>
|
||||
</a>
|
||||
</template>
|
||||
<button
|
||||
type="button"
|
||||
class="sns-item"
|
||||
:style="{
|
||||
backgroundImage: `url(${getImageHost('/images/common/ic-v2-community-link-line.png', { imageType: 'common' })})`,
|
||||
}"
|
||||
@click="handleCopy"
|
||||
>
|
||||
<span class="sr-only">copy</span>
|
||||
</button>
|
||||
<div class="close-container">
|
||||
<button
|
||||
type="button"
|
||||
class="opacity-50 z-[1] hover:opacity-100"
|
||||
@click="handleForceClose"
|
||||
>
|
||||
<span class="sr-only">close</span>
|
||||
<AtomsIconsCloseLine size="24" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-sns:hover .icon-share {
|
||||
@apply fill-white;
|
||||
}
|
||||
|
||||
.sns-list {
|
||||
@apply absolute bottom-0 right-0 flex items-center justify-center gap-4 rounded-full
|
||||
h-[40px] md:h-[48px] pl-4 pr-3
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-[rgba(255,255,255,0.06)] before:rounded-full;
|
||||
}
|
||||
.sns-item {
|
||||
@apply w-[24px] h-[24px] bg-center bg-cover bg-no-repeat opacity-50 z-[1]
|
||||
hover:opacity-100;
|
||||
}
|
||||
.sns-item:hover {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
.close-container {
|
||||
@apply relative flex pl-4
|
||||
before:content-[''] before:absolute before:top-1/2 before:left-0 before:w-[1px] before:h-[20px] before:bg-[rgba(255,255,255,0.1)] before:translate-y-[-50%];
|
||||
}
|
||||
</style>
|
||||
@@ -147,7 +147,7 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.lnb-wrap {
|
||||
@apply fixed top-1/2 right-0 py-12 pr-4 text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50;
|
||||
@apply fixed top-1/2 right-0 py-12 pr-[42px] text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50;
|
||||
}
|
||||
.main-list {
|
||||
@apply flex flex-col gap-4 items-end;
|
||||
@@ -163,10 +163,22 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
button {
|
||||
@apply font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
|
||||
@apply relative font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
|
||||
}
|
||||
button:hover,
|
||||
button.is-active {
|
||||
@apply text-[var(--lnb-active-color)];
|
||||
}
|
||||
button::before {
|
||||
@apply content-[''] absolute top-1/2 rounded-full -translate-y-1/2 bg-transparent transition-all duration-300 ease-in-out;
|
||||
}
|
||||
button.is-active::before {
|
||||
@apply bg-[var(--lnb-active-color)];
|
||||
}
|
||||
.btn-1depth::before {
|
||||
@apply -right-4 w-1.5 h-1.5;
|
||||
}
|
||||
.btn-2depth::before {
|
||||
@apply -right-3.5 w-1 h-1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataLnbMenu } from '#layers/types/api/pageData'
|
||||
|
||||
const HEADER_HEIGHT = 64
|
||||
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
const pageDataStore = usePageDataStore()
|
||||
const breakpoints = useResponsiveBreakpoints()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
const activeSection = ref<string>('')
|
||||
const observerRef = shallowRef<IntersectionObserver | null>(null)
|
||||
|
||||
/** 1뎁스 LNB 배열로 정규화 */
|
||||
const lnbRoot = computed<PageDataLnbMenu[]>(() =>
|
||||
Object.values(pageData.value?.lnb_menus || {}).filter(Boolean)
|
||||
)
|
||||
const isShowLnb = computed(() => {
|
||||
const pageDataUseLnb = pageData.value?.use_lnb ?? false
|
||||
const isDesktop = breakpoints.value.isDesktop
|
||||
const lnbRootLength = lnbRoot.value.length
|
||||
return Boolean(pageDataUseLnb && isDesktop && lnbRootLength)
|
||||
})
|
||||
const activeColor = computed(
|
||||
() => pageData.value?.lnb_text_color_code_active || 'var(--text-primary)'
|
||||
)
|
||||
const disableColor = computed(
|
||||
() => pageData.value?.lnb_text_color_code_deactive || 'var(--text-secondary)'
|
||||
)
|
||||
|
||||
const getChildren = (item?: PageDataLnbMenu) =>
|
||||
item?.children ? Object.values(item.children) : []
|
||||
|
||||
/** 1뎁스 활성 판정(자신 또는 자식) */
|
||||
const is1DepthActive = (lnbItem: PageDataLnbMenu): boolean => {
|
||||
if (!lnbItem) return false
|
||||
if (activeSection.value === lnbItem.page_ver_tmpl_name_en) return true
|
||||
return getChildren(lnbItem).some(
|
||||
c => activeSection.value === c.page_ver_tmpl_name_en
|
||||
)
|
||||
}
|
||||
|
||||
/** 스크롤 이동 */
|
||||
const scrollToLnb = async (id: string) => {
|
||||
if (import.meta.server) return
|
||||
await nextTick()
|
||||
|
||||
const el = document.getElementById(id)
|
||||
if (!el) return
|
||||
|
||||
const rect = el.getBoundingClientRect()
|
||||
const targetY = window.scrollY + rect.top - HEADER_HEIGHT
|
||||
|
||||
windowY.value = targetY
|
||||
}
|
||||
|
||||
/** 관찰 대상 id 리스트(1뎁스 + 2뎁스) */
|
||||
const sectionIds = computed<string[]>(() => {
|
||||
const ids: string[] = []
|
||||
for (const item of lnbRoot.value) {
|
||||
if (item?.page_ver_tmpl_name_en) ids.push(item.page_ver_tmpl_name_en)
|
||||
for (const c of getChildren(item)) {
|
||||
if (c?.page_ver_tmpl_name_en) ids.push(c.page_ver_tmpl_name_en)
|
||||
}
|
||||
}
|
||||
// 중복 제거
|
||||
return Array.from(new Set(ids))
|
||||
})
|
||||
|
||||
const createObserver = () => {
|
||||
if (observerRef.value) observerRef.value.disconnect()
|
||||
|
||||
observerRef.value = new IntersectionObserver(
|
||||
entries => {
|
||||
// 보이는 섹션 중 화면 상단에 가장 가까운 요소를 활성화
|
||||
const visibles = entries
|
||||
.filter(e => e.isIntersecting)
|
||||
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
|
||||
|
||||
if (visibles.length > 0) {
|
||||
activeSection.value = (visibles[0].target as HTMLElement).id
|
||||
return
|
||||
}
|
||||
|
||||
// 가끔 빠르게 스크롤 시 entries가 비어있을 수 있으므로
|
||||
// 현재 모든 섹션 중에서 '상단 기준으로 가장 가까운' 것을 폴백 계산
|
||||
const candidates: Array<{ id: string; top: number }> = []
|
||||
for (const id of sectionIds.value) {
|
||||
const el = document.getElementById(id)
|
||||
if (!el) continue
|
||||
const top = el.getBoundingClientRect().top - HEADER_HEIGHT
|
||||
candidates.push({ id, top })
|
||||
}
|
||||
// 0에 가장 가까운 양수/음수 모두 고려(위/아래 가장 가까운)
|
||||
candidates.sort((a, b) => Math.abs(a.top) - Math.abs(b.top))
|
||||
if (candidates.length) activeSection.value = candidates[0].id
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
// 상단 20%, 하단 60% 마진은 유지하되 헤더 보정치 반영
|
||||
rootMargin: `-${Math.max(HEADER_HEIGHT, 20)}px 0px -60% 0px`,
|
||||
threshold: 0,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const handleLnbClick = (lnbItem: PageDataLnbMenu) => {
|
||||
if (!lnbItem?.page_ver_tmpl_name_en) return
|
||||
scrollToLnb(lnbItem.page_ver_tmpl_name_en)
|
||||
}
|
||||
|
||||
const observeSections = () => {
|
||||
if (import.meta.server) return
|
||||
if (!observerRef.value) createObserver()
|
||||
const obs = observerRef.value!
|
||||
obs.disconnect() // 기존 관찰 해제
|
||||
|
||||
// DOM 렌더 후 관찰 등록
|
||||
requestAnimationFrame(() => {
|
||||
for (const id of sectionIds.value) {
|
||||
const el = document.getElementById(id)
|
||||
if (el) obs.observe(el)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (import.meta.server) return
|
||||
createObserver()
|
||||
observeSections()
|
||||
})
|
||||
|
||||
// lnb 데이터/DOM이 바뀌면 재관찰
|
||||
watchEffect(async () => {
|
||||
if (!import.meta.client) return
|
||||
if (isShowLnb.value && sectionIds.value.length) {
|
||||
await nextTick()
|
||||
observeSections()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
observerRef.value?.disconnect()
|
||||
observerRef.value = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="isShowLnb"
|
||||
class="lnb-wrap"
|
||||
:style="{
|
||||
'--lnb-active-color': activeColor,
|
||||
'--lnb-disable-color': disableColor,
|
||||
}"
|
||||
>
|
||||
<ul class="main-list">
|
||||
<li v-for="lnbItem in lnbRoot" :key="lnbItem.path_code">
|
||||
<button
|
||||
v-dompurify-html="lnbItem.menu_name"
|
||||
type="button"
|
||||
:class="['btn-1depth', { 'is-active': is1DepthActive(lnbItem) }]"
|
||||
@click="handleLnbClick(lnbItem)"
|
||||
></button>
|
||||
|
||||
<ul v-if="getChildren(lnbItem).length > 0" class="sub-list">
|
||||
<li v-for="subItem in getChildren(lnbItem)" :key="subItem.path_code">
|
||||
<button
|
||||
v-dompurify-html="subItem.menu_name"
|
||||
type="button"
|
||||
:class="[
|
||||
'btn-2depth',
|
||||
{
|
||||
'is-active': activeSection === subItem.page_ver_tmpl_name_en,
|
||||
},
|
||||
]"
|
||||
@click="handleLnbClick(subItem)"
|
||||
></button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lnb-wrap {
|
||||
@apply fixed top-1/2 right-0 py-12 pr-4 text-right -translate-y-1/2 bg-[radial-gradient(100%_50%_at_100%_50%,rgba(0,0,0,0.5)_25%,rgba(0,0,0,0)_100%)] z-50;
|
||||
}
|
||||
.main-list {
|
||||
@apply flex flex-col gap-4 items-end;
|
||||
}
|
||||
.btn-1depth {
|
||||
@apply text-[15px] leading-[26px] tracking-[-0.54px];
|
||||
}
|
||||
.sub-list {
|
||||
@apply flex flex-col gap-2 items-end mt-4 mb-1 pr-[46px];
|
||||
}
|
||||
.btn-2depth {
|
||||
@apply text-[14px] leading-[20px] tracking-[-0.42px];
|
||||
}
|
||||
|
||||
button {
|
||||
@apply font-[500] text-[var(--lnb-disable-color)] transition-all duration-300 ease-in-out;
|
||||
}
|
||||
button:hover,
|
||||
button.is-active {
|
||||
@apply text-[var(--lnb-active-color)];
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user