Design Pattern: Tree View
Visual basis: vortex-1.0 — verify behavior / copy / structure, not pixel style.
Why we're building this
Multiple platform features — Site Management, Floor Plan, Device lists, Custom Views, Access Control — render hierarchical data as a tree, but each built its own expand/collapse and selection behaviour, so the experience is inconsistent. The immediate trigger: Site Management moved from three levels to five (Site > Area × 4), and the existing fixed-depth tree implementations cannot support it.
This pattern defines one reusable, depth-agnostic tree view: node behaviour is driven by node kind, not depth; selection and expansion are independent; "expand all" stops at a configurable kind. It ships first in the Site Management sidebar, then replaces the Floor Plan tree, and is available to other modules (device picker, views) later.
節點結構與狀態
一個通用樹狀列表元件,顯示巢狀階層資料。元件本身不認得「Site / Area / Camera」,只認得「節點」與「種類(kind)」——icon、空狀態文案、業務語意都由使用端透過資料與設定傳入。
下圖為 Tree View 的預設狀態(以 Sites 為例):
節點類型 Node types
| 類型 | 外觀 |
|---|---|
| 可展開(無 icon) | chevron(16×16)+ label |
| 可展開(有 icon) | chevron(16×16)+ icon(20×20)+ label |
| 葉節點(無 icon) | 無箭頭,label 左側 12px padding 對齊 |
| 葉節點(有 icon) | 無箭頭,icon(20×20)+ label,左側 12px padding |
| 空提示行 Empty Hint Row | Regular 400 文字,無 icon/chevron,disabled 不可點;文字由使用端依 kind 傳入(如「No cameras」) |
節點狀態 Node states
| 狀態 | 外觀(VORTEX 1.0 token) |
|---|---|
| 預設 | 文字 @color-text01、icon @color-icon03、透明背景 |
| Hover | 文字 @color-text01、背景 @color-surface02(內層容器圓角 8px) |
| 選取 Active | 文字 @color-text01、背景 @color-surface04(內層容器圓角 8px) |
| 空容器 Empty Container | 文字 @color-text20 / icon @color-icon29(變淡);可展開、可選取;hover / selected 時恢復一般色 |
| 停用 Disabled | 文字 @color-text06 / icon @color-icon01,不可點擊 |
輔助線 Indent guides
| 狀態 | 外觀 |
|---|---|
| 預設 | 不顯示 |
| Hover | 該節點所屬最頂層群組底下所有展開層級出現微弱垂直線(@color-outline19) |
| Active | 選取節點展開後,直接子層出現較明顯垂直線(@color-outline04);收合時父層出現 |
| Active + Hover | 較明顯的線優先 |
尺寸 Dimensions
| 項目 | 值 |
|---|---|
| Node height | 40px |
| Node horizontal padding | 8px |
| Node internal gap | 4px |
| Chevron | 16×16px |
| Node icon | 20×20px |
| Indent per level | 16px |
| Leaf 左側 padding | 12px |
| 選取內層容器圓角 | 8px |
| Level-1 群組間距 | 8px |
| Container padding | 8px |
| 輔助線寬度 | 1px |
字體:節點文字 Segoe UI 16px Semibold(600) line-height 1;空提示行 Segoe UI 16px Regular(400) line-height 1(不用 italic)。
邊界 Boundaries
| 情境 | 行為 |
|---|---|
| 沒有任何資料 | 不顯示樹,由使用端處理 empty state |
| 展開的節點沒有子項目 | 顯示空提示行(Empty Hint Row),文字由使用端依 kind 提供 |
Token gap:
@color-icon29(空容器 icon 變淡)尚未加入theme-color/index.less,以 OpenSpecadd-tree-view-component為準。
TODO / 未解問題
-
@color-icon29token 尚未落地,需與前端確認補上。
選取
規則 Behaviour
- 點擊節點 label(或 icon)= 選取;一次只能選一個(single-select)。
- 點擊選取不改動任何展開/收合狀態——使用者能點到的節點必然已經可見(其祖先本來就是展開的),無需再展開。
- 選取後,元件會通知使用端「使用者選了哪個節點」,由頁面接手做後續(例如右側面板顯示該節點對應的內容)。元件本身不決定選取後的行為。
- 點擊 chevron 只切換展開/收合、不影響選取(見 Section 03)。
選取的規格本身含「展開被選節點的收合祖先」;但對使用者點擊而言這一步是 no-op(能點到,代表其祖先本來就已展開)。真正會「看到祖先被展開」的情境是程式化定位選取(Section 05,定位藏在收合祖先下的目標)與搜尋命中(Section 04,展開命中節點的祖先)。
下面兩張圖分別為「選取態」與「選取 + hover 同時」(hover 為微暗背景、選取為微亮背景):
選取「不會」做的事 釐清邊界
點擊選取只做「標記為選取」,完全不碰展開/收合狀態:
- 不改變被選取節點本身的展開/收合狀態(原本收合就維持收合)。
- 不展開被選取節點的子節點。
- 不收合其他已展開的節點。
驗收條件 QA · Given-When-Then
- Given 點擊某節點 label,Then 該節點成為唯一選取(前一個取消),且展開/收合狀態不變。
- Given 點擊 chevron,Then 只切換展開/收合、選取不變。
「選取藏在收合祖先下的節點會展開祖先」屬程式化定位選取,驗收條件見 Section 05。
元件與 Token 決策
- 選取態:文字
@color-text01、背景@color-surface04(內層容器圓角 8px)。
展開與收合
點擊分離 Click separation
[chevron] [icon] [label] [counter]
↑ 展開/收合 ↑ 選取
- chevron 區:只切換展開/收合,不影響選取。
- label / icon / counter 區:只選取,不影響展開/收合。
- 整列 hover:整列背景變化。
全部展開/全部收合
頂部節點計數列(「N Sites」)右側的單顆按鈕,即為「全部展開/全部收合」切換:
| 控制 | 行為 |
|---|---|
| 展開按鈕 icon | 全收合時顯示 chevrons-down,tooltip「Expand All」 |
| 收合按鈕 icon | 有展開節點時顯示 chevrons-up,tooltip「Collapse All」 |
| 全部展開的停點 | 可設定「全部展開」停在某一種類(如展開到 Floor plan,Camera 維持收合) |
| 初始展開 | 可設定初次載入時展開到哪一種類 |
驗收條件 QA · Given-When-Then
- Given 點擊 chevron,Then 切換該節點展開/收合、選取不變。
- Given 點「全部展開」,Then 展開到設定的停點種類為止。
- Given 已有任一節點展開,Then 全部按鈕顯示為 Collapse All(
chevrons-up)。
搜尋
搜尋的過濾、命中 highlight、自動展開祖先、展開狀態快照/還原、以及 No Matches Found 空狀態,都是元件內建的行為;使用端只提供搜尋輸入框。以下定義這些互動規則。
搜尋流程 Flow
- 使用者輸入 → 首次輸入時快照當前展開狀態。
- 即時過濾(不分大小寫)。
- 自動展開命中節點的所有祖先。
- 命中文字以 highlight 顯示;無關分支隱藏。
過濾規則 Filter rules
| 節點 | 行為 |
|---|---|
| 命中 | 名稱含關鍵字(不分大小寫),正常可選 |
| 命中的祖先 | 顯示且可選(正常樣式、不灰化),提供階層 context |
| 命中的子節點 | 預設不展開;父層也命中才展開 |
| 完全無關分支 | 隱藏 |
清除搜尋 Clear
| 操作 | 行為 |
|---|---|
| 點清除(X) | 清除搜尋文字 |
| 展開狀態 | 恢復搜尋前的快照 |
| 選取狀態 | 搜尋前有選取則恢復;搜尋中有新選取則保留新選取 |
| highlight | 全部移除 |
狀態與邊界
| 情境 | 行為 |
|---|---|
| 無命中 No Matches Found | 於 tree 區顯示「No Matches Found」空狀態(含說明文字「Please try adjusting filters or use different keywords.」),隱藏所有節點 |
| disabled 節點被命中 | 顯示但維持 disabled、不可選 |
| 選取節點仍在結果中 | 維持該節點的選取態 |
| 選取節點被過濾掉 | 選取在邏輯上保留、但不可見;清除搜尋後恢復顯示 |
| 搜尋中選了新節點 | 清除後保留新選取 |
| 搜尋中外部觸發定位選取 | 先清除搜尋、恢復展開,再執行定位選取(見 Section 05) |
驗收條件 QA · Given-When-Then
- Given 輸入關鍵字,Then 只顯示命中節點及其祖先,其餘隱藏。
- Given 命中節點,Then 自動展開其祖先,且命中文字 highlight。
- Given 清除搜尋,Then 展開狀態恢復成搜尋前的快照。
- Given disabled 節點被命中,Then 顯示但維持不可選。
- Given 搜尋無任何命中,Then 顯示「No Matches Found」空狀態並隱藏所有節點。
- Given 選取的節點被搜尋過濾掉,Then 選取邏輯上保留,清除搜尋後恢復可見。
外部觸發選取
外部事件(例如點擊地圖上的 camera marker)可觸發 tree 選取特定節點。
流程 Flow
- 外部觸發「定位並選取」某個目標節點。
- 若在搜尋中,先清除搜尋、恢復展開狀態。
- 找到目標節點 → 展開所有祖先 → 選取 → 平滑捲動到可見區。
狀態與邊界
| 情境 | 行為 |
|---|---|
| 目標已可見且已選取 | 不做任何事 |
| 目標已可見但未選取 | 選取,必要時捲動 |
| 目標不存在 | 靜默失敗,不做任何事 |
| 搜尋中觸發 | 先清除搜尋、恢復展開,再執行定位選取 |
驗收條件 QA · Given-When-Then
- Given 外部觸發定位一個收合的深層節點,Then 展開其祖先、選取、捲動到可見。
- Given 目標節點不存在,Then 靜默失敗(無錯誤、畫面不變)。
- Given 搜尋中觸發定位選取,Then 先清除搜尋並恢復展開,再執行定位選取。
TODO / 未解問題
- 鍵盤導航(方向鍵 / Enter / 空白鍵)目前不實作,設計上預留未來擴充空間。
使用場景
同一個元件、不同資料與設定,撐起平台上數種樹狀情境。元件不綁定業務語意——icon 由使用端**依節點種類(kind)**傳入。
Icon 規則:Site / Area 節點一律無 icon;icon 只用來區分不同「種類」的節點。因此:Site Management 只有 site/area → 完全無 icon;View 多了 Camera 種類 → Camera 有 icon;Floorplans 有 Floor plan + Camera 兩種 → 兩者各有 icon。
Site Management Sidebar 五層、無 icon
Site > Area × 4,純文字節點(無 icon)。這是 tree-view 的首發場景,採單選、fill 選取樣式。
View Sidebar 最多六層、Camera 有 icon
Site > Area × 4 > Camera——Camera 直接掛在最深的 Area 底下(無 Floor plan 層);Camera 由使用端依節點種類傳入 icon。
Floorplans Sidebar 最多七層、Floor plan / Camera 有 icon
Site > Area × 4 > Floor plan > Camera——比 View 多一層 Floor plan;Floor plan 與 Camera 由使用端依節點種類傳入對應 icon。
以上是「側邊欄」情境;同一個元件也用在對話框內的挑選器,差別在選取模式:
移動裝置的目標挑選器 Site Management · Move devices(單選)
Site Management 的「移動裝置到別的 Site」對話框,內嵌 Tree View 當目標挑選器:單選(single-select),並採 outline 選取樣式(淡色底+強調外框,比 sidebar 的 fill 更醒目,因為選取代表「選定的目的地」)——選一個目標 Site 或 Area,選取會取代前一個;目前所在場域標「Current」,不可選但仍可展開(讓使用者能展開它、挑其下的子場域)。詳見 Site Management spec 的「移動設備到別的 Site」。
Manage Device Access by Site 多選三態
以 Site / Area 為單位授權的權限設定也用 Tree View,採多選模式:每列 chevron 後有 20×20 三態勾選(未勾/勾選/半選)。勾一個節點會連同整個子樹一起勾;之後每個祖先自動重算(子孫全勾=勾選、部分勾=半選、全未勾=未勾)。詳見 Manage Device Access by Site spec。
狀態與邊界
| 情境 | 說明 |
|---|---|
| 層數不固定 | 節點行為由 kind 決定,不由深度決定;同一 kind 可出現在不同深度 |
| 未來取代 | 之後取代 Floor Plan 專案現有寫死層數的 tree 實作 |
驗收條件 QA · Given-When-Then
- Given Site Management 資料(最多五層、無 icon),Then 正確渲染且無 icon。
- Given View 資料(最多六層,Camera 有 icon),Then 依節點種類顯示 Camera icon。
- Given Floorplans 資料(最多七層,Floor plan / Camera 有 icon),Then 依節點種類顯示對應 icon。