fix. 버튼 컴포넌트 링크 적용, 코드 리팩토링
This commit is contained in:
@@ -1,45 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import type { GameDataKeyCodeCodes } from '#layers/types/api/gameData'
|
||||
import type { ButtonType } from '#layers/types/components/button'
|
||||
|
||||
interface ButtonProps {
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
type?: ButtonType
|
||||
icon?: string
|
||||
disabled?: boolean
|
||||
target?: '_self' | '_blank'
|
||||
href?: string
|
||||
rel?: string
|
||||
backgroundColor?: string
|
||||
backgroundImage?: string
|
||||
textColor?: string
|
||||
disabled?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
type: 'action',
|
||||
backgroundColor: 'var(--primary)',
|
||||
textColor: 'var(--alternative-02)',
|
||||
icon: '',
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
// 색상 코드 키 목록 key_code_codes
|
||||
const PARSED_KEY_CODE_CODES_KEYS: (keyof GameDataKeyCodeCodes)[] = [
|
||||
'primary',
|
||||
'text-primary',
|
||||
'text-secondary',
|
||||
'alternative-01',
|
||||
'alternative-02',
|
||||
]
|
||||
|
||||
// 색상 값을 CSS 변수로 변환하는 헬퍼 함수
|
||||
const getColorValue = (color: string) =>
|
||||
PARSED_KEY_CODE_CODES_KEYS.includes(color as keyof GameDataKeyCodeCodes)
|
||||
? `var(--${color})`
|
||||
: color
|
||||
|
||||
const buttonClasses = computed(() => [
|
||||
'btn-base group relative inline-flex items-center justify-center font-medium border border-gray-600/30 overflow-hidden',
|
||||
props.disabled ? 'cursor-default' : 'cursor-pointer',
|
||||
])
|
||||
|
||||
const buttonClasses = computed(() =>
|
||||
['btn-base group', props.class].filter(Boolean)
|
||||
)
|
||||
const buttonStyles = computed(() => {
|
||||
const styles: Record<string, string> = {
|
||||
backgroundColor: getColorValue(props.backgroundColor),
|
||||
color: getColorValue(props.textColor),
|
||||
backgroundColor: props.backgroundColor,
|
||||
color: props.textColor,
|
||||
'--text-color': props.textColor,
|
||||
}
|
||||
|
||||
if (props.backgroundImage) {
|
||||
@@ -51,40 +40,50 @@ const buttonStyles = computed(() => {
|
||||
|
||||
return styles
|
||||
})
|
||||
const componentTag = computed((): string => {
|
||||
switch (props.type) {
|
||||
case 'download':
|
||||
case 'external':
|
||||
return 'a'
|
||||
case 'internal':
|
||||
return 'AtomsLocaleLink'
|
||||
default:
|
||||
return 'button'
|
||||
}
|
||||
})
|
||||
const componentProps = computed(() => {
|
||||
const baseProps = { disabled: props.disabled }
|
||||
|
||||
const overlayClasses = computed(() => [
|
||||
'absolute inset-0 -m-px transition-opacity duration-200',
|
||||
props.disabled
|
||||
? 'opacity-20 z-10'
|
||||
: 'bg-white opacity-0 group-hover:opacity-20',
|
||||
])
|
||||
|
||||
const overlayDisabledStyles = computed(
|
||||
() =>
|
||||
props.disabled && {
|
||||
backgroundColor: props.textColor,
|
||||
if (props.type === 'external') {
|
||||
return {
|
||||
...baseProps,
|
||||
href: props.href,
|
||||
target: props.target,
|
||||
rel: props.rel,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const contentDisabledStyles = computed(() => props.disabled && { opacity: 0.2 })
|
||||
if (props.type === 'internal') {
|
||||
return {
|
||||
...baseProps,
|
||||
to: props.href,
|
||||
}
|
||||
}
|
||||
|
||||
return baseProps
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<component
|
||||
:is="componentTag"
|
||||
v-bind="componentProps"
|
||||
:class="buttonClasses"
|
||||
:style="buttonStyles"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
<!-- 호버 효과 / Disabled 오버레이 -->
|
||||
<span :class="overlayClasses" :style="overlayDisabledStyles" />
|
||||
|
||||
<!-- 버튼 내용 -->
|
||||
<span
|
||||
class="relative flex items-center gap-2"
|
||||
:style="contentDisabledStyles"
|
||||
>
|
||||
<span class="relative flex items-center gap-2 z-[1]">
|
||||
<slot />
|
||||
<span v-if="props.icon" class="flex-shrink-0" v-html="props.icon" />
|
||||
</span>
|
||||
</button>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -9,19 +9,29 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
target: '',
|
||||
class: '',
|
||||
})
|
||||
|
||||
const componentTag = computed(() => {
|
||||
return props.target === '_blank' ? 'a' : 'AtomsLocaleLink'
|
||||
})
|
||||
|
||||
const componentProps = computed(() => {
|
||||
if (props.target === '_blank') {
|
||||
return {
|
||||
href: props.to,
|
||||
target: props.target,
|
||||
class: props.class,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
to: props.to,
|
||||
class: props.class,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
v-if="props.target === '_blank'"
|
||||
v-bind="$attrs"
|
||||
:href="props.to"
|
||||
:target="props.target"
|
||||
:class="props.class"
|
||||
>
|
||||
<component :is="componentTag" v-bind="{ ...$attrs, ...componentProps }">
|
||||
<slot />
|
||||
</a>
|
||||
<AtomsLocaleLink v-else v-bind="$attrs" :to="props.to" :class="props.class">
|
||||
<slot />
|
||||
</AtomsLocaleLink>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { getResponsiveSrc } from '#layers/utils/dataUtil'
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
@@ -13,39 +12,14 @@ const props = defineProps<Props>()
|
||||
const displayText = computed(() => {
|
||||
return props.resourcesData?.display?.txt || ''
|
||||
})
|
||||
|
||||
// 이미지 소스 추출
|
||||
const imageSrc = computed(() => {
|
||||
return getResponsiveSrc(props.resourcesData?.res_path)
|
||||
})
|
||||
|
||||
// 색상 코드 추출 (우선순위: color_code_txt > color_code)
|
||||
const colorCode = computed(() => {
|
||||
return (
|
||||
props.resourcesData?.display?.color_code_txt ||
|
||||
props.resourcesData?.display?.color_code
|
||||
)
|
||||
})
|
||||
|
||||
// 색상 이름 추출 (우선순위: color_name_txt > color_name)
|
||||
const colorName = computed(() => {
|
||||
return (
|
||||
props.resourcesData?.display?.color_name_txt ||
|
||||
props.resourcesData?.display?.color_name
|
||||
)
|
||||
return props.resourcesData?.display?.color_name
|
||||
})
|
||||
|
||||
// 색상 스타일 계산
|
||||
const textStyles = computed(() => {
|
||||
const styles: Record<string, string> = {}
|
||||
|
||||
if (colorName.value) {
|
||||
styles.color = `var(--${colorName.value})`
|
||||
} else if (colorCode.value) {
|
||||
styles.color = colorCode.value
|
||||
}
|
||||
|
||||
return styles
|
||||
const colorCode = computed(() => {
|
||||
return props.resourcesData?.display?.color_code
|
||||
})
|
||||
|
||||
// HTML 콘텐츠 정리 (줄바꿈 처리)
|
||||
@@ -82,7 +56,7 @@ const hasImage = computed(() => {
|
||||
<span
|
||||
v-else-if="displayText"
|
||||
v-dompurify-html="sanitizedContent"
|
||||
:style="textStyles"
|
||||
:style="{ color: getColorCode({ colorName, colorCode }) }"
|
||||
class="block"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { getResponsiveClass, getResponsiveSrc } from '#layers/utils/dataUtil'
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,25 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import type { PageDataResourceGroup } from '#layers/types/api/pageData'
|
||||
import type {
|
||||
PageDataResourceGroup,
|
||||
PageDataResourceGroupBtnInfo,
|
||||
} from '#layers/types/api/pageData'
|
||||
import type { ButtonType } from '#layers/types/components/button'
|
||||
|
||||
const props = defineProps<{
|
||||
interface ButtonListProps {
|
||||
resourcesData: PageDataResourceGroup[]
|
||||
buttonType?: string
|
||||
}>()
|
||||
}
|
||||
|
||||
const props = defineProps<ButtonListProps>()
|
||||
|
||||
// 상수 정의
|
||||
const BUTTON_TYPE_MAP = {
|
||||
URL: {
|
||||
_self: 'internal' as const,
|
||||
_blank: 'external' as const,
|
||||
},
|
||||
DOWNLOAD: 'download' as const,
|
||||
} as const
|
||||
const DEFAULT_BUTTON_TYPE: ButtonType = 'action'
|
||||
|
||||
const getButtonType = (btnInfo: PageDataResourceGroupBtnInfo): ButtonType => {
|
||||
const btnType = btnInfo?.detail?.btn_type
|
||||
const btnTarget = btnInfo?.detail?.action?.link_target
|
||||
|
||||
if (btnType === 'URL' && btnTarget) {
|
||||
return BUTTON_TYPE_MAP.URL[btnTarget] || DEFAULT_BUTTON_TYPE
|
||||
}
|
||||
|
||||
if (btnType === 'DOWNLOAD') {
|
||||
return BUTTON_TYPE_MAP.DOWNLOAD
|
||||
}
|
||||
|
||||
return DEFAULT_BUTTON_TYPE
|
||||
}
|
||||
|
||||
const getButtonProps = (button: PageDataResourceGroup) => ({
|
||||
type: getButtonType(button.btn_info),
|
||||
target: button.btn_info?.detail?.action?.link_target,
|
||||
href: button.btn_info?.detail?.action?.url,
|
||||
rel: button.btn_info?.detail?.action?.rel,
|
||||
backgroundColor: getColorCode({
|
||||
colorName: button.btn_info?.color_name_btn,
|
||||
colorCode: button.btn_info?.color_code_btn,
|
||||
}),
|
||||
textColor: getColorCode({
|
||||
colorName: button.btn_info?.color_name_txt,
|
||||
colorCode: button.btn_info?.color_code_txt,
|
||||
}),
|
||||
disabled: button.btn_info?.disabled,
|
||||
text: button.btn_info?.txt_btn_name,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="props.resourcesData"
|
||||
v-if="props.resourcesData?.length"
|
||||
class="flex flex-wrap justify-center gap-3 md:gap-4"
|
||||
>
|
||||
<AtomsButton
|
||||
v-for="button in props.resourcesData"
|
||||
:key="button.group_code"
|
||||
:button-type="props.buttonType"
|
||||
v-for="(button, index) in props.resourcesData"
|
||||
:key="`${button.group_code}-${index}`"
|
||||
v-bind="getButtonProps(button)"
|
||||
class="size-extra-small md:size-medium"
|
||||
:background-color="button.btn_info?.color_code_btn"
|
||||
:text-color="button.btn_info?.color_code_txt"
|
||||
:disabled="button.btn_info?.disabled"
|
||||
>
|
||||
{{ button.btn_info?.txt_btn_name }}
|
||||
</AtomsButton>
|
||||
|
||||
Reference in New Issue
Block a user