Appearance
컨펌 만들기
@jood/v-modal
을 사용해서 아래와 같은 컨펌창을 만들 수 있습니다.
vue
<template>
<div>
<el-button type="primary" @click="onSave">컨펌</el-button>
</div>
</template>
<script lang="ts" setup>
import { useConfirm } from './useConfirm';
const { open: openConfirm } = useConfirm();
const onSave = () => {
save();
};
const save = async () => {
const confirm = await openConfirm<number>({
message: '안녕하세요. 컨펌창 예제 입니다.',
actions: [
{ result: 0, label: '취소' },
{ result: 1, label: '확인', type: 'primary' },
],
});
if (confirm !== 1) {
return;
}
alert('확인 클릭!');
};
</script>
<style lang="scss" scoped></style>
ts
import { onUnmounted } from 'vue';
import { type ModalData, JdModalRef, useJdModalService } from '@jood/v-modal';
import type { Subscription } from 'rxjs';
import type { ConfirmData } from './types';
import ConfirmBox from './ConfirmBox.vue';
/**
* 컨펌
*/
export const useConfirm = () => {
const modalService = useJdModalService();
let modalRef: JdModalRef | null = null;
let closeListener: Subscription | null = null;
const beforeUsedHistoryStrategy = {
disabledHistory: true,
beforeValue: true,
};
const open = <Result = any>(data: ConfirmData, modalData?: Partial<ModalData>) => {
dispose();
const { disabledHistory = true } = data;
if (disabledHistory) {
beforeUsedHistoryStrategy.disabledHistory = true;
beforeUsedHistoryStrategy.beforeValue = modalService.usedHistoryStrategy;
modalService.setUseHistoryStrategy(false);
}
let promiseResolver: (result: PromiseLike<Result>) => void;
modalRef = modalService.open({
data,
component: ConfirmBox,
disableShadow: true,
floatingMode: false,
overlayClose: false,
...modalData,
});
closeListener = modalRef.observeClosed().subscribe((result) => {
if (promiseResolver) promiseResolver(result);
if (closeListener) {
closeListener.unsubscribe();
closeListener = null;
}
});
return new Promise<Result>((resolve) => {
promiseResolver = resolve;
});
};
const dispose = () => {
if (beforeUsedHistoryStrategy.disabledHistory) {
modalService.setUseHistoryStrategy(beforeUsedHistoryStrategy.beforeValue);
}
if (closeListener) {
closeListener.unsubscribe();
closeListener = null;
}
if (modalRef) {
modalRef.close();
modalRef = null;
}
};
onUnmounted(() => {
dispose();
});
return {
open,
};
};
vue
<template>
<div class="confirm-box" :class="classes">
<template v-if="viewState.hasHead">
<div class="cb-head">
<div class="tit">{{ modalData.title }}</div>
</div>
</template>
<div class="cb-body">
<template v-if="modalData.messageComponent">
<component :is="modalData.messageComponent"></component>
</template>
<template v-else>
<div class="message" v-html="modalData.message"></div>
</template>
</div>
<div class="cb-actions">
<button
v-for="(action, index) in modalData.actions"
:key="index"
:class="action.type"
class="action-button"
@click="onAction(action)"
>
<span class="label">{{ action.label }}</span>
</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useJdModalRef } from '@jood/v-modal';
import type { ConfirmAction, ConfirmData } from './types';
const modalRef = useJdModalRef<unknown, ConfirmData>();
const modalData = modalRef.data || ({} as ConfirmData);
const viewState = computed(() => {
const { title } = modalData;
const hasHead = !!title;
return {
hasHead,
};
});
const classes = computed(() => {
const { hasHead } = viewState.value;
return {
'has-head': hasHead,
};
});
/**
* 액션 버튼 클릭
*/
const onAction = (action: ConfirmAction) => {
modalRef.close(action.result);
};
</script>
<style lang="scss" scoped>
.confirm-box {
padding: 20px;
max-width: 94vw;
width: 360px;
border-radius: 10px;
box-sizing: border-box;
background: #fff;
&.has-head {
.cb-body {
padding-top: 20px;
font-size: 15px;
}
}
.cb-head {
font-size: 16px;
font-weight: bold;
text-align: center;
word-break: break-all;
color: #111;
}
.cb-body {
padding: 20px 0 30px 0;
font-size: 16px;
color: #333;
text-align: left;
word-break: break-all;
white-space: pre-line;
}
.cb-actions {
display: flex;
align-items: center;
justify-content: flex-end;
.action-button {
position: relative;
display: block;
margin: 0 2px;
font-size: 13px;
font-weight: bold;
letter-spacing: -0.2px;
text-align: center;
color: #444;
border: none;
border-radius: 4px;
border: 1px solid #ccc;
appearance: none;
background-color: #fff;
cursor: pointer;
&:focus-visible {
outline: 1px solid #222;
}
.label {
display: flex;
align-items: center;
justify-content: center;
padding: 0 20px;
min-width: 72px;
height: 36px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
}
&:hover {
opacity: 0.9;
}
&.negative {
color: #666;
border-color: #ccc;
background: #fff;
}
&.primary {
color: #fff;
border-color: #0095ff;
background: #0095ff;
}
&.danger {
color: #fff;
border-color: #d33838;
background: #d33838;
}
}
}
}
</style>
ts
import { type Component } from 'vue';
export interface ConfirmData<Result = unknown> {
title?: string; // 컨펌 타이틀
message?: string; // 컨펌 메세지
actions: ConfirmAction<Result>[]; // 컨펌 버튼
messageComponent?: Component; // 메세지 영역에 컴포넌트를 넣어서 사용하려는 경우
disabledHistory?: boolean; // 모달 히스토리 처리 disable 여부
}
export interface ConfirmAction<Result = unknown> {
type?: 'primary' | 'danger' | 'warning' | '' | undefined;
label: string;
result?: Result;
}