From d530d095bde98b10b3a3ddbf54d75346e0d97b7f Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Mon, 26 Oct 2026 13:13:28 +0100 Subject: [PATCH] :sparkles: Show an indicator on component copies with a missing main A copy whose main component cannot be found looked like any other copy, so users kept expecting updates that never came. The component pill in the Design tab now shows a broken link icon, with a tooltip, when the main component was deleted or its file is not connected. A selection that mixes broken and healthy copies shows no indicator for now, until design decides how it should look. Relates to #12134 AI-assisted-by: claude-opus-5-5 --- .../sidebar/options/menus/component.cljs | 28 +++++++++++++++++-- .../sidebar/options/menus/component.scss | 12 ++++++++ frontend/translations/en.po | 4 +++ frontend/translations/es.po | 4 +++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 5b4f23d1db..21515e2914 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -40,6 +40,7 @@ [app.main.ui.ds.controls.switch :refer [switch*]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]] + [app.main.ui.ds.tooltip.tooltip :refer [tooltip*]] [app.main.ui.hooks :as h] [app.main.ui.workspace.sidebar.assets.common :as cmm] [app.main.ui.workspace.sidebar.options.menus.variants-help-modal] @@ -859,7 +860,7 @@ :on-enter-group on-enter-group}]))]]]])) (mf/defc component-pill* - [{:keys [icon text subtext menu-entries disabled on-click]}] + [{:keys [icon text subtext menu-entries disabled on-click is-broken]}] (let [menu-open* (mf/use-state false) menu-open? (deref menu-open*) @@ -886,7 +887,8 @@ [:div {:class (stl/css :pill)} [:button {:class (stl/css-case :pill-btn true - :with-menu menu-entries?) + :with-menu menu-entries? + :broken is-broken) :data-testid "component-pill-button" :on-click on-click :disabled disabled} @@ -900,7 +902,13 @@ text] (when subtext [:div {:class (stl/css :pill-btn-subtext)} - subtext])]] + subtext])] + + (when ^boolean is-broken + [:> tooltip* {:content (tr "workspace.options.component.broken") + :class (stl/css :pill-btn-broken)} + [:> icon* {:icon-id i/broken-link + :size "s"}]])] (when menu-entries? [:div {:class (stl/css :pill-actions)} @@ -924,6 +932,15 @@ :on-click (partial do-action action)} [:span {:class (stl/css :pill-actions-dropdown-item-label)} title]]))]]])])) +(defn- broken-copy? + "True when the shape is a component copy whose main component cannot + be found: its library is not connected to the file, or the component + was deleted." + [shape component] + (and (not (ctk/main-instance? shape)) + (or (nil? component) + (true? (:deleted component))))) + (mf/defc component-menu* [{:keys [shapes is-swap-opened]}] (let [current-file-id (mf/use-ctx ctx/current-file-id) @@ -951,6 +968,10 @@ {:include-deleted? true}) shapes) same-variant? (ctv/same-variant? components) + ;; TODO: a selection that mixes broken and healthy copies shows + ;; no indicator until design decides how it should look (#12134). + all-broken? (every? true? (map broken-copy? shapes components)) + ;; For when it's only one shape shape (first shapes) id (:id shape) @@ -1076,6 +1097,7 @@ (cpn/merge-path-item-with-dot path (:name component)))) :on-click open-component-panel :disabled (or is-swap-opened (not can-swap?)) + :is-broken all-broken? :menu-entries menu-entries}] (when (and is-variant? main-instance?) [:> icon-button* {:variant "ghost" diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss index 5fd8e97fc0..50d2dbb9a6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.scss @@ -534,6 +534,10 @@ border-radius: $br-8 0 0 $br-8; } + &.broken { + grid-template-columns: var(--sp-l) 1fr auto; + } + &:focus { outline: $b-1 solid var(--color-accent-primary); } @@ -544,6 +548,14 @@ } } +// Marks a copy whose main component cannot be found. +.pill-btn-broken { + display: flex; + block-size: $sz-32; + align-items: center; + color: var(--color-accent-primary-muted); +} + .pill-btn-icon { display: flex; block-size: $sz-32; diff --git a/frontend/translations/en.po b/frontend/translations/en.po index dde95de91f..a98e20239d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -7127,6 +7127,10 @@ msgstr "Component" msgid "workspace.options.component.annotation" msgstr "Annotation" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:908 +msgid "workspace.options.component.broken" +msgstr "Main component not found. This copy won’t receive updates." + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1036 msgid "workspace.options.component.copy" msgstr "Copy" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 1bd162954c..45fbd730b4 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -7212,6 +7212,10 @@ msgstr "Componente" msgid "workspace.options.component.annotation" msgstr "Nota" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:908 +msgid "workspace.options.component.broken" +msgstr "No se encuentra el componente principal. Esta copia no recibirá actualizaciones." + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1036 msgid "workspace.options.component.copy" msgstr "Copia"