feat. ScrollTop 버튼 추가
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/* Button Size Classes */
|
||||
@layer components {
|
||||
.modal-wrap {
|
||||
@apply fixed inset-0 flex p-5 z-[500];
|
||||
@apply fixed inset-0 flex p-5 z-[800];
|
||||
}
|
||||
|
||||
.modal-wrap.dimmed {
|
||||
|
||||
69
layers/components/atoms/Button/ScrollTop.vue
Normal file
69
layers/components/atoms/Button/ScrollTop.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
parentRef: HTMLElement | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { y: windowY } = useWindowScroll({ behavior: 'smooth' })
|
||||
const { height: viewportH } = useWindowSize()
|
||||
|
||||
const parentEl = toRef(props, 'parentRef')
|
||||
const { bottom: parentBottom } = useElementBounding(parentEl)
|
||||
|
||||
const showBtn = computed(() => windowY.value > 0)
|
||||
|
||||
const offsetY = 12
|
||||
|
||||
const pinToParent = computed(() => {
|
||||
if (!parentBottom.value) return false
|
||||
return parentBottom.value <= viewportH.value - offsetY
|
||||
})
|
||||
|
||||
const positionClasses = computed(() =>
|
||||
pinToParent.value
|
||||
? 'absolute bottom-[12px] right-[12px] md:bottom-[40px] md:right-[40px]'
|
||||
: 'fixed bottom-[12px] right-[12px] md:bottom-[40px] md:right-[40px]'
|
||||
)
|
||||
|
||||
const handleScrollToTop = () => {
|
||||
windowY.value = 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<button
|
||||
v-if="showBtn"
|
||||
:class="['btn-top', positionClasses]"
|
||||
@click="handleScrollToTop"
|
||||
>
|
||||
<AtomsIconsTopLine class="icon-top" />
|
||||
<span class="sr-only">top</span>
|
||||
</button>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-top {
|
||||
@apply rounded-full flex items-center justify-center bg-black/20 z-[100]
|
||||
w-[40px] h-[40px] md:w-[48px] md:h-[48px]
|
||||
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 before:transition-all before:duration-300 before:ease-in-out
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:rounded-full after:opacity-0 after:transition-all after:duration-300 after:ease-in-out;
|
||||
}
|
||||
.btn-top:hover {
|
||||
@apply after:opacity-10;
|
||||
}
|
||||
.btn-top:hover .icon-top {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.icon-top {
|
||||
@apply opacity-50 transition-opacity duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
[data-theme='light'] {
|
||||
.btn-top {
|
||||
@apply bg-white/10;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
layers/components/atoms/icons/TopLine.vue
Normal file
30
layers/components/atoms/icons/TopLine.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
size?: number | string
|
||||
color?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
size: 24,
|
||||
color: 'white',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M13 9.41422L17.2929 13.7071C17.6834 14.0976 18.3166 14.0976 18.7071 13.7071C19.0976 13.3166 19.0976 12.6834 18.7071 12.2929L12.7078 6.29361C12.5289 6.1143 12.2822 6.00257 12.0094 6.00005C12.0063 6.00002 12.0032 6.00001 12 6.00001C11.9968 6.00001 11.9937 6.00002 11.9906 6.00005C11.7269 6.00249 11.4877 6.10694 11.3104 6.27585C11.3045 6.28145 11.2987 6.28714 11.2929 6.2929L5.29289 12.2929C4.90237 12.6834 4.90237 13.3166 5.29289 13.7071C5.68341 14.0976 6.31658 14.0976 6.7071 13.7071L11 9.41422L11 20C11 20.5523 11.4477 21 12 21C12.5523 21 13 20.5523 13 20L13 9.41422Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<path
|
||||
d="M19.5 4.00001C19.5 4.55229 19.0523 5 18.5 5L5.5 5C4.94771 5 4.5 4.55228 4.5 4C4.5 3.44772 4.94771 3 5.5 3L18.5 3C19.0523 3 19.5 3.44772 19.5 4.00001Z"
|
||||
:fill="color"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -25,16 +25,13 @@ const canTeleport = (localId: string) => {
|
||||
|
||||
<style scoped>
|
||||
.spinner-container {
|
||||
@apply fixed inset-0 bg-black/90 flex items-center justify-center z-[900];
|
||||
@apply fixed inset-0 flex items-center justify-center z-[900];
|
||||
}
|
||||
.spinner {
|
||||
@apply w-[80px] h-[80px] bg-cover bg-center bg-no-repeat bg-[url('/images/common/publisning_template_loader_black.png')];
|
||||
}
|
||||
|
||||
[data-theme='light'] {
|
||||
.spinner-container {
|
||||
@apply bg-white/90;
|
||||
}
|
||||
.spinner {
|
||||
@apply bg-[url('/images/common/publisning_template_loader_white.png')];
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ onUnmounted(() => {
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75"
|
||||
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-75 z-[800]"
|
||||
:class="props.modalName"
|
||||
@click="handleOutsideClick"
|
||||
>
|
||||
|
||||
@@ -312,7 +312,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
@apply bg-theme-foreground text-theme-foreground-reversal relative z-50;
|
||||
@apply bg-theme-foreground text-theme-foreground-reversal relative z-[100];
|
||||
}
|
||||
.game-wrapper {
|
||||
@apply absolute flex w-full h-[48px] items-center whitespace-nowrap px-[52px] bg-theme-foreground sm:px-[72px] md:h-16 md:pl-0 md:pr-[40px]
|
||||
|
||||
@@ -12,6 +12,8 @@ interface Props {
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const mainRef = ref<HTMLElement>()
|
||||
|
||||
// 템플릿 레지스트리 사용
|
||||
const { getTemplateComponent } = useTemplateRegistry()
|
||||
|
||||
@@ -54,7 +56,7 @@ watchEffect(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="main">
|
||||
<main ref="mainRef" class="main">
|
||||
<template
|
||||
v-for="(template, index) in visibleTemplates"
|
||||
:key="template.template_code ?? index"
|
||||
@@ -65,11 +67,15 @@ watchEffect(() => {
|
||||
:page-ver-tmpl-seq="template.page_ver_tmpl_seq"
|
||||
/>
|
||||
</template>
|
||||
<AtomsButtonScrollTop
|
||||
v-if="pageData.use_top_btn ?? false"
|
||||
:parent-ref="mainRef"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.main {
|
||||
@apply pt-[48px] md:pt-[64px];
|
||||
@apply relative pt-[48px] md:pt-[64px];
|
||||
}
|
||||
</style>
|
||||
|
||||
114
layers/templates/GrBoard01/index.vue
Normal file
114
layers/templates/GrBoard01/index.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
import { SplideSlide } from '@splidejs/vue-splide'
|
||||
import {
|
||||
getComponentContainer,
|
||||
getComponentGroup,
|
||||
} from '#layers/utils/dataUtil'
|
||||
import type { Splide as SplideType } from '@splidejs/splide'
|
||||
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
||||
import type { OperateGroupItem } from '#layers/types/api/resourcesData'
|
||||
|
||||
interface Props {
|
||||
components: PageDataTemplateComponents
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const pageDataStore = usePageDataStore()
|
||||
const { getResourcesData } = useResourcesData()
|
||||
const { locale } = useI18n()
|
||||
const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
|
||||
|
||||
const { pageData } = storeToRefs(pageDataStore)
|
||||
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
const mainTitleData = computed(() =>
|
||||
getComponentGroup(props.components, 'mainTitle')
|
||||
)
|
||||
|
||||
// const buttonListData = ref(
|
||||
// getComponentGroupAry(slideData?.value[0], 'buttonList')
|
||||
// )
|
||||
|
||||
const { data: slideData } = await useAsyncData(
|
||||
`gr-board-01-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`,
|
||||
async () => {
|
||||
if (!pageData.value?.page_seq || !pageData.value?.page_ver) {
|
||||
return []
|
||||
}
|
||||
|
||||
const operateGroupList = await getResourcesData({
|
||||
pageSeq: pageData.value.page_seq,
|
||||
pageVer: pageData.value.page_ver,
|
||||
pageVerTmplSeq: props.pageVerTmplSeq,
|
||||
langCode: locale.value,
|
||||
})
|
||||
|
||||
console.log('operateGroupList', operateGroupList)
|
||||
|
||||
const bannerList = getComponentContainer(operateGroupList, 'bannerList', {
|
||||
hasGroup: true,
|
||||
minLength: 4,
|
||||
}) as OperateGroupItem[]
|
||||
|
||||
return bannerList
|
||||
},
|
||||
{
|
||||
default: () => [],
|
||||
server: false,
|
||||
}
|
||||
)
|
||||
|
||||
const onArrowClick = (direction, targetIndex) => {
|
||||
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
|
||||
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]
|
||||
sendLog(locale.value, useAnalyticsLogDataDirect(logTracking, 1))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="relative py-[80px] md:py-[120px]">
|
||||
<WidgetsBackground v-if="backgroundData" :resources-data="backgroundData" />
|
||||
<div class="section-content px-0">
|
||||
<WidgetsMainTitle
|
||||
v-if="mainTitleData"
|
||||
:resources-data="mainTitleData"
|
||||
class="title-md"
|
||||
/>
|
||||
<BlocksSlideDefault
|
||||
v-if="slideData"
|
||||
:slide-item-length="slideData?.length"
|
||||
:pagination="false"
|
||||
class="mt-[24px] md:mt-[48px]"
|
||||
@arrow-click="onArrowClick"
|
||||
>
|
||||
<SplideSlide v-for="(item, index) in slideData" :key="index">
|
||||
<div class="slide-inner border-line">
|
||||
<BlocksVisualContent
|
||||
:resources-data="getComponentGroup(item, 'imgList')"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
object-fit="cover"
|
||||
:alt="getComponentGroup(item, 'subTitle')?.display?.text"
|
||||
/>
|
||||
</div>
|
||||
</SplideSlide>
|
||||
</BlocksSlideDefault>
|
||||
<!-- <WidgetsButtonList
|
||||
v-if="buttonListData"
|
||||
:resources-data="buttonListData"
|
||||
:page-ver-tmpl-seq="props.pageVerTmplSeq"
|
||||
class="mt-[40px] md:mt-[56px]"
|
||||
/> -->
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section-container {
|
||||
@apply before:hidden md:before:block before:content-[''] before:absolute before:top-0 before:left-0 before:w-[104px] before:h-full before:bg-gradient-to-l from-transparent to-[rgba(0,0,0,0.7)]
|
||||
after:hidden md:after:block after:content-[''] after:absolute after:top-0 after:right-0 after:w-[104px] after:h-full after:bg-gradient-to-r from-transparent to-[rgba(0,0,0,0.7)];
|
||||
}
|
||||
</style>
|
||||
@@ -115,7 +115,7 @@ const handleSplideMove = (_splide: SplideType, newIndex: number) => {
|
||||
|
||||
<style scoped>
|
||||
.splide-pagination {
|
||||
@apply flex items-center absolute bottom-10 left-1/2 -translate-x-1/2 z-10 md:bottom-[96px];
|
||||
@apply flex items-center absolute bottom-10 left-1/2 -translate-x-1/2 z-[5] md:bottom-[96px];
|
||||
}
|
||||
.btn-pagination {
|
||||
@apply relative;
|
||||
|
||||
Reference in New Issue
Block a user