SwModal

SwModal 基於 reka-ui Dialog 封裝,透過 DialogPortal 將對話框內容傳送到 <body> 下渲染,避免 CSS 層疊上下文問題。元件提供標題、描述、主體內容和底部操作的完整結構,並支援透過插槽完全自訂各區域。

裝配前置:SwModalHost

framework 同時提供 SwModalHost 容器元件,是命令式 useModal 渲染掛載點。app.vue 頂層放置一次即可:

<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
  <SwModalHost />
</template>

由 sw create 生成的專案已內建,多數情況無需手動加。SwModalHost 僅服務於 useModal() 命令式對話框;宣告式 <SwModal> 直接掛在元件樹裡不依賴此容器。

宣告式用法

透過 v-model:open 綁定顯示狀態,配合 title、description、confirm-text prop 快速建構確認對話框。

基本用法

加载中...

自訂底部

透過 #footer 插槽可以完全替換預設的確認/取消按鈕,實現多按鈕版面、條件禁用等複雜互動場景。

自訂底部

加载中...

命令式用法(useModal)

useModal().open() → Promise<boolean>

加载中...

除了在模板中宣告 <SwModal>,框架還提供命令式 API,在業務邏輯中直接 await 對話框結果,適合確認刪除、提示等場景。

第一步: 在 app.vue 放置一次 <SwModalHost />(sw create 生成的專案已內建,無需手動新增):

<!-- app.vue -->
<template>
  <NuxtLayout><NuxtPage /></NuxtLayout>
  <SwModalHost />
  <SwToast />
</template>

第二步: 在任意元件中呼叫 useModal().open():

<script setup lang="ts">
const modal = useModal()

async function handleDelete() {
  const confirmed = await modal.open({
    title: '確認刪除',
    description: '此操作不可撤銷,確定繼續嗎?',
    confirmText: '刪除',
    cancelText: '取消',
  })
  if (confirmed) await deleteItem()
}

// 純提示(隱藏取消按鈕)
async function handleAlert() {
  await modal.open({
    title: '溫馨提示',
    description: '活動已結束',
    confirmText: '知道了',
    cancelText: '',
  })
}
</script>

open() 返回 Promise<boolean>:使用者點確認 resolve true,點取消或關閉 resolve false。同一時刻只有一個命令式對話框,多次呼叫會覆蓋前一個。

SwModalHost 是基礎設施元件,不接受任何 props,放在 app.vue 一次即可覆蓋全應用。

預設皮膚與 token 覆蓋

SwModal 自帶預設皮膚(framework 注入 modal.css):--sw-modal-* 變數預設映射語義 token,自動跟隨明暗主題;業務在 overrides.css 平級 :root 重定義 --sw-modal-* 即可換皮(彈窗 Teleport 到 body,scoped 覆蓋不到);整套皮膚可用 framework.styles.modal = false 關閉。

彈窗層級由 --sw-modal-z 控制(預設 1000,fw#332 z 層旋鈕)——與頁面內其它浮層撞層時改它,別去 :deep 覆蓋。

皮膚 token 覆蓋(圓角 / 寬度 / 品牌色)

加载中...

幾何:視口安全預設值 + per-dialog 定製

彈窗幾何預設視口安全:width: min(--sw-modal-width(28rem), 100dvw - 2rem)、max-height: min(--sw-modal-max-height(85dvh), 100dvh - 2rem)——窄屏保留 1rem 邊距,長內容自動「頭尾釘住、體內滾動」(body 內部出縱向滾動條),不會溢出視口。

class / attrs 會透傳到 .sw-modal-content(彈窗內容根元素),這是 per-dialog 定製的官方通道——在業務 class 裡定義 --sw-modal-width / --sw-modal-max-height 即可單彈窗定寬高(:root 重定義則是全站所有彈窗):

<SwModal v-model:open="visible" class="wide-dialog" title="批次編輯">…</SwModal>

<style>
/* 非 scoped:content Teleport 到 body,scoped 屬性到不了 */
.wide-dialog {
  --sw-modal-width: 44rem;
}
</style>

內容超寬時 body 會出橫向滾動條(有意保留的暴露信號,靜默截斷會藏 bug)——正解是經 class 通道給該彈窗定合適寬度,而不是在內容裡硬撐 min-width。

關閉行為(dismissible / closable)

鎖定彈窗 + 裸內容彈窗

加载中...

屬性(Props)

名稱類型必填預設值說明
openboolean✓—控制對話框顯示/隱藏,搭配 v-model:open 使用
titlestring—對話框標題文字;也可使用 #title 插槽自訂
descriptionstring—描述文字;當沒有 default 插槽時才渲染
confirmTextstring—確認按鈕文字;提供此 prop 時才顯示底部操作列
cancelTextstring'取消'取消按鈕文字,傳空字串則隱藏取消按鈕
closablebooleantrue是否顯示關閉 ✕;傳 false 且無 title 時為無 header/✕ 的裸內容彈窗
dismissiblebooleantrue是否允許點遮罩 / Esc 關閉;傳 false 鎖死(僅 ✕ / 按鈕 / 業務程式碼可關)

事件(Events)

事件名載荷類型說明
update:openboolean對話框顯示狀態變化時觸發
confirmvoid點擊確認按鈕時觸發
cancelvoid點擊取消按鈕時觸發

插槽(Slots)

名稱說明
default對話框主體內容區域
title自訂標題區域
footer自訂底部操作區域,覆蓋預設的確認/取消按鈕

注意事項

  • 受控與命令式兩套用法別在同一檔案混用(Rule SM-001)—— <SwModal v-model:open="visible"> 與 useModal().open({...}) 都改全域彈窗狀態,同一元件內混用會導致狀態錯亂(一次操作彈兩個 / 互相覆蓋關閉)。二選一:區域彈窗用受控,全域確認框用命令式。
  • <SwModalHost /> 必須在 app.vue 根層掛一次(Rule SM-002)—— 沒掛則 useModal().open() 什麼都不顯示;掛在深層路由元件則路由切換時被銷毀、命令式彈窗失效。framework 有 runtime 兜底:client 端偵測到未掛載會 console.warn 一次(用 useState 去重防洗版),dev / prod 均生效。
  • 焦點管理已內建,別自建 overlay —— 基於 reka-ui Dialog,開彈層聚焦 / focus trap / Esc 關閉 / 關閉還焦全部自帶(Rule UX-005)。自寫 <div v-if="open"> 遮罩會讓鍵盤與螢幕閱讀器使用者無法操作。
  • 它不是零樣式元件 —— framework 預設注入 modal.css(毛玻璃遮罩 + 完整卡片視覺),applier 零自訂樣式時就是可直接用的觀感。別按「無樣式對話框」預期從頭寫皮膚,先看上面預設皮膚與 token 覆蓋:優先覆寫 --sw-modal-*(無需 :deep 穿透),確需完全自繪才用 framework.styles.modal = false 關掉整套。