Anchored Buy Rail
錨定購買欄:左欄長捲動、右欄黏性購買面板,捲動時以 FLIP 濃縮成迷你結帳膠囊,逼近評論區再展開。
載入預覽⋯
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
3280
4180
96
<AnchoredBuyRail />
安裝
npx shadcn@latest add https://webberui.com/r/anchored-buy-rail.json或在 components.json 設定 registries 後,改用 @webberui/anchored-buy-rail 安裝。
安裝依賴後,從 registry JSON(/r/anchored-buy-rail.json 的 files[0].content)複製 anchored-buy-rail.tsx 原始碼到你的 components/ui/ 目錄:
npm install motion clsx tailwind-merge lucide-react使用
import { AnchoredBuyRail } from "@/components/ui/anchored-buy-rail";
<AnchoredBuyRail
title="Terra 有機棉衛衣"
price={1280}
compareAtPrice={1680}
currency="NT$"
variants={[
{
id: "size",
label: "尺寸",
options: [
{ value: "s", label: "S" },
{ value: "m", label: "M" },
{ value: "l", label: "L" },
],
},
]}
onAddToCart={(selection) => console.log(selection)}
footer={<Reviews />}
>
<ProductStory />
</AnchoredBuyRail>左欄放入自由捲動的 children(媒體與描述區塊),右欄購買面板由 props 驅動並自動黏性跟隨。若元件被放進巢狀 overflow 捲動容器,請把容器的 ref 傳給 container。
Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
title | string | — | 商品名稱 |
price | number | — | 價格,搭配 currency 格式化 |
compareAtPrice | number | — | 原價(劃線顯示) |
currency | string | "$" | 貨幣符號 |
thumbnail | ReactNode | — | 商品縮圖,會在面板與膠囊間 FLIP 遷移 |
subtitle | string | — | 副標;無變體時顯示於膠囊 |
variants | BuyVariantGroup[] | — | 變體單選群組(尺寸、顏色…) |
value | BuySelection | — | 受控選取值 |
defaultValue | BuySelection | 各組首個可選項 | 非受控初始選取值 |
onValueChange | (v: BuySelection) => void | — | 選取變更回呼 |
onAddToCart | (v: BuySelection) => void | — | 按下 CTA 的回呼 |
ctaLabel | string | "加入購物車" | CTA 文案 |
note | ReactNode | 免運訊息 | 面板底部的信任訊息 |
children | ReactNode | — | 左欄自由捲動內容 |
footer | ReactNode | — | 頁尾區塊(評論區);逼近時面板展開 |
container | RefObject<HTMLElement | null> | 視窗 | 巢狀捲動容器 ref |
stickyTop | number | 16 | 黏性吸頂位置(px) |
condenseAfter | number | 96 | 捲離頂端多少 px 後收攏成膠囊 |
expandMargin | number | 160 | 距頁尾多少 px 內提前展開 |
BuyVariantGroup / BuyOption
interface BuyVariantGroup {
id: string;
label: string;
options: { value: string; label: string; disabled?: boolean }[];
}
type BuySelection = Record<string, string>; // { [群組 id]: 選項 value }細節
- 黏性 + FLIP:右欄以
position: sticky跟隨捲動;價格、縮圖與 CTA 帶相同layoutId,收攏/展開時由 Motion 的共享 layout 動畫(FLIP)在完整面板與膠囊之間連續補間。 - 收攏時機:以兩個哨兵搭配
IntersectionObserver判定——頂端哨兵捲離吸頂線condenseAfter後收攏;footer上緣進入視口(含expandMargin提前量)時展開。因此在視窗或任意巢狀捲動容器內都能運作。 - 受控/非受控:提供
value即為受控;否則以defaultValue(或各群組第一個可選項)維護內部狀態。
可及性
- 變體群組為
role="radiogroup",選項為role="radio"並帶aria-checked;採 roving tabindex,支援方向鍵在群組內移動選取,disabled選項自動跳過。 - CTA 為原生
<button>,focus 有可見的 focus ring。 - 購買面板為
role="region"並帶aria-label。 - 使用者開啟「減少動態效果」時,面板恆保持完整態、停用收攏與 FLIP 動畫,所有購買控制項全程可用。