♻️ Update import modal UI (#11704)

* 🌐 Translate import dialog strings

Several plain hardcoded strings in the import dialog (the file
rename aria-label, library-resolution messages, table headers, and
the manually-linked summary labels) were never wired to the i18n
system. Wire them up via tr and add the corresponding English and
Spanish entries to the translation catalogs.

* 🐛 Fix library-resolution summary layout in import dialog

The auto-linked/manually-linked summary list had several layout bugs:
name/selection columns stretched unevenly, the auto-linked badge sat
nested inside the name's ellipsis text (unreliable across browsers),
and a long unbreakable library name could grow the whole modal past
its fixed width because a bare 1fr grid track has no minimum-size
cap. Fix the column sizing, split the badge out as a sibling of the
name, and clamp the modal content column with minmax(0, 1fr).

*  Add Skip button to library resolution wizard

Add a "Skip" action to the per-file library resolution step so users
can leave all of a file's pending libraries unconnected and move on
to the next one, instead of being forced to pick a candidate or go
back. Clears any pending selections for the current file before
advancing the wizard, so the summary correctly shows those libraries
as unlinked.

* 🐛 Fix stale translation key on library resolution button

The primary action button in the library resolution wizard referenced
"dashboard.import.review-links", a translation key that no longer
existed, leaving the button with no text. Rename it to
"dashboard.import.connect-selected-libraries" to match the actual
button label.

* 🐛 Align no-selection state with arrow and file name

.summary-no-selection was missing display:flex/align-items:center,
so its icon and text weren't vertically centered against each other
or against the file name column in the import library summary.

* 💄 Use select-accent color for auto-linked badge border

* 💄 Change import summary columns from 50/50 to 40/60 split

Give the "new library" column more room than the "original library"
column in the manually-linked libraries table (header and rows), since
it usually carries the longer "name (project)" text.

* 🐛 Divide back buttons from forward buttons

* 🐛 Fix dropdown typography
This commit is contained in:
Eva Marco 2026-09-21 12:29:14 +02:00 committed by GitHub
parent dc8160c13a
commit 52825f528c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 216 additions and 112 deletions

View File

@ -293,8 +293,7 @@
[:input {:type "text"
:auto-focus true
:class (stl/css :file-name-input)
;;TODO: Add translation for aria-label
:aria-label "File name"
:aria-label (tr "dashboard.import.file-name-label")
:default-value (:name entry)
:on-key-press on-edit-key-press
:on-blur on-edit-blur}]]
@ -412,7 +411,7 @@
[:> text* {:class (stl/css :library-resolution-message)
:as "p"
:typography t/body-large}
"Some libraries couldn't be linked automatically. Select the correct library for each:"]
(tr "dashboard.import.resolve-libraries")]
[:table {:class (stl/css :library-resolution-table)}
@ -422,13 +421,13 @@
[:> icon* {:icon-id i/library
:class (stl/css :library-resolution-icon)
:size "s"}]
"original library"]
(tr "dashboard.import.resolve-libraries.original-library")]
[:th {:class (stl/css :library-resolution-arrow)}]
[:th {:class (stl/css :library-resolution-connection)}
[:> icon* {:icon-id i/library
:class (stl/css :library-resolution-icon)
:size "s"}]
"connect to"]]]
(tr "dashboard.import.resolve-libraries.connect-to")]]]
[:tbody {:class (stl/css :library-resolution-body)}
(for [{:keys [id name candidates]} candidates]
(let [options (mapv (fn [c]
@ -477,54 +476,53 @@
:typography t/body-medium}
(:name resolution-file)]]
(when (seq done)
[:div {:class (stl/css :summary-section)}
[:ul {:class (stl/css :summary-list)}
(for [{:keys [name]} done]
[:li {:class (stl/css :summary-list-item)
:key (dm/str name)}
[:span {:class (stl/css :summary-item-name)} name]
[:span {:class (stl/css :summary-linked-badge)}
[:> icon* {:icon-id i/status-tick
:class (stl/css :summary-badge-icon)
:size "s"}]
(tr "dashboard.import.summary.linked")]])]])
[:div {:class (stl/css :summary-body)}
(when (seq done)
[:div {:class (stl/css :summary-section)}
[:ul {:class (stl/css :summary-list)}
(for [{:keys [name]} done]
[:li {:class (stl/css :summary-list-item)
:key (dm/str name)}
[:span {:class (stl/css :summary-item-name)} name]
[:span {:class (stl/css :summary-linked-badge)}
(tr "dashboard.import.summary.linked")]])]])
(when (seq pending)
[:div {:class (stl/css :summary-section)}
[:div {:class (stl/css :summary-section-header)}
;; TODO: Add translation for this string
(when (seq pending)
[:div {:class (stl/css :summary-section)}
[:div {:class (stl/css :summary-section-header)}
[:> text* {:as "span"
:class (stl/css :summary-section-title)
:typography t/headline-small}
(tr "dashboard.import.summary.manually-linked")]]
[:ul {:class (stl/css :summary-list)}
[:li {:class (stl/css :summary-list-item)
:key "summary-list-header"}
[:span {:class (stl/css :summary-item-name-header)}
(tr "dashboard.import.summary.original")]
[:> text* {:as "span"
:class (stl/css :summary-section-title)
:typography t/headline-small}
"linked manually"]]
[:ul {:class (stl/css :summary-list)}
[:li {:class (stl/css :summary-list-item)
:key "summary-list-header"}
[:span {:class (stl/css :summary-item-name-header)}
"Original"]
[:span {:class (stl/css :summary-item-name-header)}
(tr "dashboard.import.summary.new")]]
(for [{:keys [id name] :as cand} pending]
(let [selected-id (get selection id)
selected-c (when selected-id
(d/seek #(= (str (:id %)) (str selected-id)) (:candidates cand)))]
[:li {:class (stl/css :summary-list-item)
:key (dm/str id)}
[:span {:class (stl/css-case :summary-item-name true
:summary-item-base true)} name]
[:span {:class (stl/css :summary-item-name-header)}
"New"]]
(for [{:keys [id name] :as cand} pending]
(let [selected-id (get selection id)
selected-c (when selected-id
(d/seek #(= (str (:id %)) (str selected-id)) (:candidates cand)))]
[:li {:class (stl/css :summary-list-item)
:key (dm/str id)}
[:span {:class (stl/css :summary-item-name)} name]
[:> icon* {:icon-id i/row
:size "m"
:class (stl/css :summary-linked-arrow)}]
(if selected-c
[:span {:class (stl/css :summary-linked-info)}
[:span {:class (stl/css :summary-linked-name)}
(:name selected-c)]
[:span {:class (stl/css :summary-linked-project)}
(:project-name selected-c)]]
[:span {:class (stl/css :summary-no-selection)}
(tr "dashboard.import.summary.no-selection")])]))]])]))
(if selected-c
[:span {:class (stl/css :summary-linked-info)}
[:> icon* {:icon-id i/row
:size "m"
:class (stl/css :summary-linked-arrow)}]
[:span {:class (stl/css :summary-linked-name)}
(dm/str (:name selected-c) " (" (:project-name selected-c) ")")]]
[:span {:class (stl/css :summary-no-selection)}
[:> icon* {:icon-id i/row
:size "m"
:class (stl/css :summary-linked-arrow)}]
(tr "dashboard.import.summary.no-selection")])]))]])]]))
(mf/defc library-resolution-summary*
{::mf/private true}
@ -649,7 +647,7 @@
{::mf/private true}
[{:keys [current-unresolved-file selection on-select
visited all-visited?
on-wizard-prev on-wizard-next]}]
on-wizard-prev on-wizard-next on-wizard-skip]}]
[:*
[:div {:class (stl/css :modal-content)}
[:> library-resolution*
@ -664,12 +662,18 @@
:variant "secondary"
:on-click on-wizard-prev}
(tr "labels.previous")])
[:> button* {:class (stl/css :accept-btn)
:variant "primary"
:on-click on-wizard-next}
(if all-visited?
(tr "labels.next")
(tr "dashboard.import.review-links"))]]]])
[:div {:class (stl/css :action-buttons-end)}
[:> button* {:class (stl/css :secondary-btn)
:variant "secondary"
:on-click on-wizard-skip}
(tr "labels.skip")]
[:> button* {:class (stl/css :accept-btn)
:variant "primary"
:on-click on-wizard-next}
(if all-visited?
(tr "labels.next")
(tr "dashboard.import.connect-selected-libraries"))]]]]])
(mf/defc import-library-summary-stage*
{::mf/private true}
@ -688,10 +692,11 @@
:variant "secondary"
:on-click on-summary-back}
(tr "labels.back")])
[:> button* {:class (stl/css :accept-btn)
:variant "primary"
:on-click on-confirm-library-links}
(tr "dashboard.import.confirm-library-links")]]]])
[:div {:class (stl/css :action-buttons-end)}
[:> button* {:class (stl/css :accept-btn)
:variant "primary"
:on-click on-confirm-library-links}
(tr "dashboard.import.confirm-library-links")]]]]])
(mf/defc import-dialog
{::mf/register modal/components
@ -855,6 +860,15 @@
(let [file-id (:id current-unresolved-file)]
(swap! visited* disj file-id))))
on-wizard-skip
(mf/use-fn
(mf/deps current-unresolved-file)
(fn []
(let [file-id (:id current-unresolved-file)
pending-ids (mapv :id (:pending current-unresolved-file))]
(swap! selection* #(apply dissoc % pending-ids))
(swap! visited* conj file-id))))
on-summary-back
(mf/use-fn
(mf/deps visited)
@ -971,7 +985,8 @@
:visited visited
:all-visited? all-visited?
:on-wizard-prev on-wizard-prev
:on-wizard-next on-wizard-next}]
:on-wizard-next on-wizard-next
:on-wizard-skip on-wizard-skip}]
:library-summary
[:> import-library-summary-stage*

View File

@ -42,9 +42,15 @@
flex: 1;
overflow: hidden auto;
display: grid;
grid-template-columns: 1fr;
// minmax(0, 1fr), not a bare 1fr: a bare 1fr track's minimum size
// defaults to its content's min-content width, so a long unbreakable
// string deep inside (e.g. a library name with no spaces) can grow
// this column - and the fixed-width modal around it - past its
// intended size, since overflow:hidden can't shrink a track that
// already expanded to fit its content.
grid-template-columns: minmax(0, 1fr);
gap: var(--sp-l);
margin-block-end: var(--sp-xxl);
min-block-size: px2rem(40);
}
@ -71,6 +77,15 @@
gap: var(--sp-l);
}
// Groups the trailing buttons (e.g. skip/accept) and pins them to the end,
// leaving any leading button (e.g. previous/back) at the start - regardless
// of whether that leading button is rendered.
.action-buttons-end {
display: flex;
gap: var(--sp-l);
margin-inline-start: auto;
}
.file-entry {
--file-entry-fg-color: var(--color-foreground-secondary);
@ -246,17 +261,20 @@
}
.library-resolution-message {
@include use-typography("body-large");
color: var(--color-foreground-secondary);
margin-block-end: var(--sp-s);
}
.library-resolution-header {
@include use-typography("body-medium");
@include use-typography("body-small");
color: var(--color-foreground-secondary);
display: grid;
grid-template-columns: 1fr 32px 1fr;
border-block-end: $b-1 solid var(--color-foreground-secondary);
font-weight: 500;
grid-template-columns: 1fr $sz-32 1fr;
border-block-end: $b-1 solid var(--color-background-quaternary);
}
.library-origin-name,
@ -270,9 +288,10 @@
.library-resolution-item {
display: grid;
grid-template-columns: 1fr 32px 1fr;
block-size: $sz-32;
margin-block: var(--sp-s);
grid-template-columns: 1fr $sz-32 1fr;
block-size: $sz-40;
margin-block-start: var(--sp-s);
border-block-end: $b-1 solid var(--color-background-quaternary);
}
.library-resolution-icon {
@ -330,6 +349,12 @@
background: var(--color-background-primary);
}
.summary-body {
display: flex;
flex-direction: column;
gap: var(--sp-xxl);
}
.summary-list {
display: flex;
flex-direction: column;
@ -354,6 +379,7 @@
.summary-linked-arrow {
color: var(--color-foreground-secondary);
flex-shrink: 0;
}
.summary-file-icon {
@ -383,36 +409,39 @@
.summary-list-item {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
gap: var(--sp-s);
padding: var(--sp-xs) var(--sp-s);
padding-block: var(--sp-xs);
padding-inline-start: var(--sp-s);
border-radius: $br-4;
&:hover {
background: var(--color-background-secondary);
}
}
.summary-item-name {
@include use-typography("body-small");
color: var(--color-foreground-primary);
flex: 1;
flex: 0 1 auto;
min-inline-size: 0;
display: block;
max-inline-size: 99%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.summary-item-name-header {
@include use-typography("body-small");
color: var(--color-foreground-secondary);
flex: 1;
flex: 2 1 0%;
min-inline-size: 0;
display: block;
max-inline-size: 99%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:last-child {
flex: 3 1 0%;
}
}
// Auto-linked badge
@ -420,10 +449,12 @@
display: inline-flex;
align-items: center;
gap: var(--sp-xs);
padding: var(--sp-xxs) var(--sp-s);
border-radius: $br-12;
padding-block: var(--sp-xxs);
padding-inline: var(--sp-s);
border-radius: $br-8;
background: var(--color-accent-success-bg);
color: var(--color-accent-success);
border: $b-1 solid var(--color-accent-background-select);
font-size: px2rem(11);
font-weight: 500;
flex-shrink: 0;
@ -437,39 +468,39 @@
.summary-linked-info {
display: flex;
align-items: center;
gap: var(--sp-xs);
flex-shrink: 0;
gap: var(--sp-s);
flex: 3 1 0%;
min-inline-size: 0;
}
.summary-linked-name {
// TODO: Typography does not match any existing DS typography token.
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(12);
line-height: 1.4;
color: var(--color-foreground-secondary);
@include use-typography("body-small");
color: var(--color-foreground-primary);
font-weight: 500;
min-inline-size: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.summary-linked-project {
// TODO: Typography does not match any existing DS typography token.
font-family: "worksans", "vazirmatn", sans-serif;
font-weight: 400;
line-height: 1.4;
.summary-item-base {
flex: 2 1 0%;
color: var(--color-foreground-secondary);
padding: var(--sp-xxs) var(--sp-s);
border-radius: $br-12;
background: var(--color-background-quaternary);
font-size: px2rem(11);
}
// No selection state
.summary-no-selection {
// TODO: Typography does not match any existing DS typography token.
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(12);
font-weight: 400;
line-height: 1.4;
@include use-typography("body-small");
display: flex;
align-items: center;
gap: var(--sp-s);
color: var(--color-foreground-secondary);
font-style: italic;
flex-shrink: 0;
flex: 3 1 0%;
min-inline-size: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -44,6 +44,8 @@
}
.option-text {
@include use-typography("body-small");
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@ -788,6 +788,10 @@ msgstr "Confirm library links"
msgid "dashboard.import.error"
msgstr "Import failed. Please try again"
#: src/app/main/ui/dashboard/import.cljs:296
msgid "dashboard.import.file-name-label"
msgstr "File name"
#: src/app/main/ui/dashboard/import.cljs:292
#, unused
msgid "dashboard.import.import-error"
@ -869,9 +873,17 @@ msgstr ""
msgid "dashboard.import.resolve-libraries-summary"
msgstr "Review the library links before confirming:"
#: src/app/main/ui/dashboard/import.cljs:672
msgid "dashboard.import.review-links"
msgstr "Review links"
#: src/app/main/ui/dashboard/import.cljs:430
msgid "dashboard.import.resolve-libraries.connect-to"
msgstr "connect to"
#: src/app/main/ui/dashboard/import.cljs:424
msgid "dashboard.import.resolve-libraries.original-library"
msgstr "original library"
#: src/app/main/ui/dashboard/import.cljs:675
msgid "dashboard.import.connect-selected-libraries"
msgstr "Connect selected libraries"
#, unused
msgid "dashboard.import.summary.auto-linked"
@ -879,12 +891,24 @@ msgstr "Auto-linked"
#: src/app/main/ui/dashboard/import.cljs:491
msgid "dashboard.import.summary.linked"
msgstr "Linked"
msgstr "auto-linked"
#: src/app/main/ui/dashboard/import.cljs:498
msgid "dashboard.import.summary.manually-linked"
msgstr "linked manually"
#: src/app/main/ui/dashboard/import.cljs:506
msgid "dashboard.import.summary.new"
msgstr "New"
#: src/app/main/ui/dashboard/import.cljs:527
msgid "dashboard.import.summary.no-selection"
msgstr "No library selected"
#: src/app/main/ui/dashboard/import.cljs:503
msgid "dashboard.import.summary.original"
msgstr "Original"
#, unused
msgid "dashboard.import.summary.your-selection"
msgstr "Your selection"
@ -3719,6 +3743,10 @@ msgstr "Show only your comments"
msgid "labels.sketch"
msgstr "Sketch"
#: src/app/main/ui/dashboard/import.cljs:668
msgid "labels.skip"
msgstr "Skip"
#: src/app/main/ui/dashboard/sidebar.cljs:1144
msgid "labels.sources"
msgstr "Sources"

View File

@ -786,6 +786,10 @@ msgstr "Confirmar vínculos de biblioteca"
msgid "dashboard.import.error"
msgstr "La importación ha fallado. Intentalo de nuevo, por favor"
#: src/app/main/ui/dashboard/import.cljs:296
msgid "dashboard.import.file-name-label"
msgstr "Nombre del archivo"
#: src/app/main/ui/dashboard/import.cljs:292
#, unused
msgid "dashboard.import.import-error"
@ -867,9 +871,17 @@ msgstr ""
msgid "dashboard.import.resolve-libraries-summary"
msgstr "Revisa los vínculos de biblioteca antes de confirmar:"
#: src/app/main/ui/dashboard/import.cljs:672
msgid "dashboard.import.review-links"
msgstr "Revisar vínculos"
#: src/app/main/ui/dashboard/import.cljs:430
msgid "dashboard.import.resolve-libraries.connect-to"
msgstr "conectar a"
#: src/app/main/ui/dashboard/import.cljs:424
msgid "dashboard.import.resolve-libraries.original-library"
msgstr "biblioteca original"
#: src/app/main/ui/dashboard/import.cljs:675
msgid "dashboard.import.connect-selected-libraries"
msgstr "Conectar bibliotecas seleccionadas"
#, unused
msgid "dashboard.import.summary.auto-linked"
@ -877,12 +889,24 @@ msgstr "Vinculadas automáticamente"
#: src/app/main/ui/dashboard/import.cljs:491
msgid "dashboard.import.summary.linked"
msgstr "Vinculada"
msgstr "auto-vinculada"
#: src/app/main/ui/dashboard/import.cljs:498
msgid "dashboard.import.summary.manually-linked"
msgstr "vinculada manualmente"
#: src/app/main/ui/dashboard/import.cljs:506
msgid "dashboard.import.summary.new"
msgstr "Nueva"
#: src/app/main/ui/dashboard/import.cljs:527
msgid "dashboard.import.summary.no-selection"
msgstr "Ninguna biblioteca seleccionada"
#: src/app/main/ui/dashboard/import.cljs:503
msgid "dashboard.import.summary.original"
msgstr "Original"
#, unused
msgid "dashboard.import.summary.your-selection"
msgstr "Tu selección"
@ -3620,6 +3644,10 @@ msgstr "Mostrar sólo tus comentarios"
msgid "labels.sketch"
msgstr "Sketch"
#: src/app/main/ui/dashboard/import.cljs:668
msgid "labels.skip"
msgstr "Saltar"
#: src/app/main/ui/dashboard/sidebar.cljs:1144
msgid "labels.sources"
msgstr "Recursos"