在第一部分中,我們探討了微互動如何提升大量操作欄的使用者體驗。在本文中,我們將深入探討實現這些互動並同時確保靈活性、效能和可存取性的技術工程挑戰。
第一個難題是確定根據容器寬度可以容納多少個按鈕。單靠 CSS 是不夠的。
我使用ResizeObserver建立了一個自訂鉤子useVisibleChildrenCount
來動態計算可見專案的數量:
export function useVisibleChildrenCount({ containerEl, isEnabled, gap = 0 }) {
const [visibleCount, setVisibleCount] = useState(0)
const measureVisibleItems = useCallback(() => {
if (!containerEl) return
const childrenEls = Array.from(containerEl.children).filter(
(el) => el instanceof HTMLDivElement
)
let totalWidth = 0
let fitCount = 0
for (let i = 0; i < childrenEls.length; i++) {
const childWidth = childrenEls[i].offsetWidth
const spaceRequired = totalWidth === 0 ? childWidth : childWidth + gap
if (totalWidth + spaceRequired <= containerEl.offsetWidth) {
totalWidth += spaceRequired
fitCount += 1
} else {
break
}
}
setVisibleCount(fitCount)
}, [containerEl, gap])
useEffect(() => {
if (!isEnabled || !containerEl) return
const observer = new ResizeObserver(() => {
window.requestAnimationFrame(measureVisibleItems)
})
observer.observe(containerEl)
measureVisibleItems()
return () => observer.disconnect()
}, [containerEl, isEnabled, measureVisibleItems])
return isEnabled ? visibleCount : containerEl?.children.length || 0
}
這個鉤子允許元件隨著容器尺寸的變化而即時適應。
Calendly 使用react-aria-components
來實現可存取的選單。處理在桌面和行動裝置上表現不同的嵌套子選單帶來了挑戰:
MenuTrigger/MenuContent 結構是剛性的。
移動子選單需要堆疊為覆蓋層。
子選單深度是動態的和遞歸的。
在我們的第一個方法中,我們嘗試將子選單渲染到下拉式選單專案內部的入口中。我們的想法是利用入口在視覺上堆疊子選單,同時保持父選單打開。然而,React Aria 嚴格的 DOM 結構和焦點管理要求使得這種方法不可行,因為它打破了菜單內容在層級結構中應位於何處的假設。
最終的解決方案是在 MenuContent 中管理子選單堆疊狀態,其中活動子選單將完全取代根內容。在行動裝置上,當觸發子選單時,我們將根內容節點與子選單節點交換,模擬頁面堆疊的體驗。這使我們能夠保留 React Aria 的可存取性功能,同時完全控制子選單的過渡效果。
Calendly 的主打產品左側邊欄是固定的,這會導致內容區域偏移。我最初實現的 BulkActionsBar 是以視窗為中心的,這會導致對齊不均勻。
為了解決這個問題,我們採用了「Outlet Pattern」:
渲染與主內容區域對齊的包裝器 Outlet 元件。
將 ref 傳遞給該 Outlet。
使用門戶策略將 BulkActionsBar 呈現到此出口。
無論側邊欄是否存在,這種方法都能確保視覺對齊。
管理遞歸子選單需要處理焦點、深度堆疊和鍵盤導航,並且不需要對子選單層級進行任何假設。我設計了一種根據上下文進行調整的遞歸渲染策略:
在桌面上:內聯彈出視窗。
在行動裝置上:堆疊覆蓋。
鍵盤焦點是動態管理的,確保在不同深度上實現無縫的導航體驗。
小型 UI 元件可能會帶來重大的架構考量。
設計系統約束需要靈活且強大的工程解決方案。
可存取性不能是事後才想到的-它是元件架構的基礎。
設計靈活的 BulkActionsBar 需要創造性的問題解決能力以及對使用者體驗和無障礙功能細節的細緻關注。它體現了周到的元件設計如何超越視覺樣式、交叉架構、使用者流程和實際產品的限制。
特別感謝 Calendly 設計系統團隊的協作構思和問題解決會議,使得該元件成為可能。