超商取貨選店
超商取貨三段選擇:通路→縣市/區→門市清單,含搜尋、營業資訊與已選門市摘要卡;品牌中性、資料由 props 提供
這是 WebberUI Pro 元件
上線活動期間免費:註冊或登入後,在上方預覽區按「複製安裝指令」就能直接安裝,不需付費、不用綁信用卡。下方那條指令未登入時會回 401。
台灣電商結帳最常見的「超商取貨」流程,做成一個自足的步驟式面板:第一步用色塊 chips 選通路,第二步從門市資料自動推導出縣市與鄉鎮市區下拉,第三步在可搜尋的清單裡挑門市——每筆顯示地址、營業時間、24 小時徽章與(可選的)距離文字。點選門市後面板收合成摘要卡(通路色塊+門市名+地址+「更換」按鈕)並回呼 onSelect(store)。步驟切換用水平滑動過場、清單項目依序進場、選中項打勾;支援受控 value 與非受控 defaultValue、isLoading 載入骨架與全部可覆寫的文案。元件不含任何真實品牌 logo 或名稱,通路與門市資料一律由 props 傳入。
載入預覽⋯
npx shadcn@latest add "https://webberui.com/r/convenience-store-pickup.json?t=<安裝 token>"Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<ConvenienceStorePickup />
安裝
npx shadcn@latest add "https://webberui.com/r/convenience-store-pickup.json?t=<安裝 token>"或在 components.json 設定 registries 後,改用 @webberui/convenience-store-pickup 安裝。
使用
import {
ConvenienceStorePickup,
type PickupChain,
type PickupStore,
} from "@/components/ui/convenience-store-pickup";
// 通路與門市皆由你的 API 提供;此處為虛構示意
const chains: PickupChain[] = [
{ id: "sudah", name: "速達超商", color: "#e8541e" },
{ id: "neighbor", name: "好鄰居便利店", color: "#2e7d32" },
];
const stores: PickupStore[] = [
{
id: "sd-001",
chainId: "sudah",
name: "大安樂活門市",
address: "臺北市大安區樂活路 12 號",
city: "臺北市",
district: "大安區",
open24h: true,
distanceText: "350 公尺",
},
{
id: "nb-001",
chainId: "neighbor",
name: "大安木棉店",
address: "臺北市大安區木棉巷 6 號",
city: "臺北市",
district: "大安區",
hours: "07:00–22:30",
},
];
// 受控:把門市 id 存進結帳表單狀態
<ConvenienceStorePickup
chains={chains}
stores={stores}
value={form.pickupStoreId}
onChange={(id) => setForm({ ...form, pickupStoreId: id })}
onSelect={(store) => console.log("取貨門市", store.name, store.address)}
/>
// 非受控:只想知道最後選了誰
<ConvenienceStorePickup
chains={chains}
stores={stores}
defaultValue="sd-001"
onSelect={(store) => console.log(store.id)}
compact
/>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
chains | PickupChain[] | — | 通路清單,決定第一步 chips 的內容與順序 |
stores | PickupStore[] | — | 門市清單;縣市/區選項與門市清單都由這份資料推導 |
value | string | null | — | 受控值:目前選中的門市 id(null 代表尚未選擇);不提供時為非受控模式 |
defaultValue | string | null | null | 非受控模式的初始門市 id |
onSelect | (store: PickupStore) => void | — | 使用者點選門市時回呼,帶入完整門市物件 |
onChange | (storeId: string) => void | — | 選中門市 id 變動時回呼;受控模式請在此更新 value |
title | string | — | 面板標題;等同 labels.title 的捷徑,兩者同時提供時以此為準 |
labels | Partial<ConvenienceStorePickupLabels> | 內建繁中 | 覆寫介面文案(部分覆寫即可,其餘沿用內建) |
isLoading | boolean | false | 顯示載入骨架(門市資料尚未就緒時) |
showDistance | boolean | true | 是否顯示門市的 distanceText |
compact | boolean | false | 緊湊模式:縮小間距、隱藏提示、步驟副標與清單裡的營業時間,適合放進結帳側欄 |
className | string | — | 透傳到最外層容器 |
PickupChain
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 通路唯一 id,對應 PickupStore.chainId |
name | string | — | 通路顯示名稱 |
color | string | — | 代表色(任何合法 CSS 顏色字串),用於 chips 與摘要卡的色塊;省略時為中性灰 |
PickupStore
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
id | string | — | 門市唯一 id,也是 value 的內容 |
chainId | string | — | 所屬通路 id |
name | string | — | 門市名稱 |
address | string | — | 完整地址(清單與摘要卡顯示) |
city | string | — | 縣市,用來推導第二步的縣市下拉 |
district | string | — | 鄉鎮市區,用來推導第二步的區下拉 |
hours | string | — | 營業時間文字,例如「07:00–23:00」 |
open24h | boolean | — | 是否 24 小時營業;為 true 時顯示徽章並優先於 hours |
distanceText | string | — | 距離文字(例如「350 公尺」),由呼叫端算好傳入;showDistance 為 false 時不顯示 |
ConvenienceStorePickupLabels
| 欄位 | 型別 | 預設值 | 說明 |
|---|---|---|---|
title | string | "超商取貨" | 面板標題 |
stepChain | string | "取貨通路" | 步驟一名稱 |
stepArea | string | "取貨地區" | 步驟二名稱 |
stepStore | string | "取貨門市" | 步驟三名稱 |
chainHint | string | "請選擇要取貨的超商通路" | 步驟一提示文字 |
areaHint | string | "請選擇縣市與鄉鎮市區" | 步驟二提示文字 |
city | string | "縣市" | 縣市下拉的標籤 |
district | string | "鄉鎮市區" | 鄉鎮市區下拉的標籤 |
cityPlaceholder | string | "選擇縣市" | 縣市下拉未選時的佔位文字 |
districtPlaceholder | string | "選擇鄉鎮市區" | 鄉鎮市區下拉未選時的佔位文字 |
next | string | "查看門市" | 步驟二已選好地區後前往門市清單的按鈕文字 |
searchPlaceholder | string | "搜尋門市名稱或地址" | 門市搜尋框的佔位文字 |
storeUnit | string | "間門市" | 門市數量的單位(顯示為「N 間門市」) |
open24h | string | "24 小時" | 24 小時營業徽章文字 |
emptyChains | string | "目前沒有可選的通路" | 沒有任何通路可選時的空狀態文字 |
emptyAreas | string | "此通路目前沒有可取貨的門市" | 該通路沒有任何門市時的空狀態文字 |
emptyStores | string | "找不到符合的門市" | 搜尋或篩選後沒有門市時的空狀態文字 |
selected | string | "已選門市" | 摘要卡標題 |
change | string | "更換" | 摘要卡上重新選擇的按鈕文字 |
loading | string | "門市資料載入中…" | 載入骨架的無障礙朗讀文字 |
另外具名匯出 filterPickupStores(stores, { chainId, city, district, query }),與元件內部相同的篩選邏輯(關鍵字同時比對門市名稱與地址、忽略大小寫),方便在表單層計算門市數量或做伺服器端預篩。
細節
- 三步導覽:步驟列可點回已完成的步驟;尚未達成前置條件的步驟(例如還沒選通路就想選門市)為停用。換通路會清掉已選縣市/區,同一通路則保留。選好區後自動前進到門市清單,回到第二步時另有「查看門市」按鈕。
- 選項由資料推導:縣市與鄉鎮市區皆從該通路的
stores去重取得,並保留你傳入的排序(例如由北到南);不需要另外維護行政區表。 - 收合成摘要卡:點選門市後步驟面板收合、只留摘要卡,按「更換」重新展開並直接回到該門市所在的清單、預先把它設為 active。受控模式下
value從外部變成null也會自動展開面板。 - 距離文字由你決定:元件不做定位或距離計算,只顯示
distanceText;showDistance={false}可整體隱藏。 - 動畫:步驟切換為方向感知的水平滑動(前進向左、後退向右)、清單前 8 筆依序進場、選中打勾用彈簧縮放、摘要卡以高度展開。
可及性
- 步驟列為
ol清單,目前步驟以aria-current="step"標示,未達前置條件的步驟以disabled停用 - 通路 chips 為
role="group"內的aria-pressed按鈕;縣市/區使用原生select(含label),行動裝置直接呼叫系統選單 - 門市清單為
role="listbox"(可 Tab 聚焦)搭配role="option"與aria-selected,以aria-activedescendant指向目前項目;支援 ↑/↓/Home/End 移動、Enter/Space 選取,搜尋框按 ↓ 可直接跳進清單,Enter 選取目前項目(中文輸入法組字中不會誤觸) - 選定門市後焦點自動移到摘要卡的「更換」按鈕;按「更換」後焦點回到搜尋框,鍵盤流程不斷裂;門市數量以
aria-live="polite"朗讀 isLoading骨架以role="status"、aria-busy與 sr-only 文字告知載入中- 使用者系統開啟「減少動態效果」時,步驟切換改為淡入淡出、清單不再依序進場、打勾與摘要卡取消縮放/高度動畫,只保留即時切換