🎉 Improve div structure

This commit is contained in:
Eva Marco 2026-07-27 14:21:31 +02:00
parent a13d9e78f6
commit 83112af669
2 changed files with 274 additions and 366 deletions

View File

@ -20,6 +20,7 @@
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
[app.main.ui.ds.controls.select :refer [select*]] [app.main.ui.ds.controls.select :refer [select*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.foundations.assets.raw-svg :as rsvg :refer [raw-svg*]] [app.main.ui.ds.foundations.assets.raw-svg :as rsvg :refer [raw-svg*]]
@ -307,7 +308,7 @@
:typography t/body-medium} :typography t/body-medium}
(:name entry) (:name entry)
(when ^boolean is-shared? (when ^boolean is-shared?
[:> icon* {:icon-id i/library :class (stl/css :icon)}])] [:> icon* {:icon-id i/library :class (stl/css :file-label-icon)}])]
[:> context-notification* [:> context-notification*
{:level level {:level level
:appearance :ghost :appearance :ghost
@ -317,7 +318,7 @@
:typography t/body-medium} :typography t/body-medium}
(:name entry) (:name entry)
(when ^boolean is-shared? (when ^boolean is-shared?
[:> icon* {:icon-id i/library :class (stl/css :icon)}])]])]) [:> icon* {:icon-id i/library :class (stl/css :file-label-icon)}])]])])
(when ^boolean (or editable? can-be-deleted) (when ^boolean (or editable? can-be-deleted)
[:div {:class (stl/css :edit-entry-buttons)} [:div {:class (stl/css :edit-entry-buttons)}
@ -392,7 +393,15 @@
(mf/defc library-resolution* (mf/defc library-resolution*
{::mf/private true} {::mf/private true}
[{:keys [unresolved-file selection on-select]}] [{:keys [unresolved-file selection on-select]}]
(let [candidates (:pending unresolved-file)] (let [candidates (:pending unresolved-file)
disconnected* (mf/use-state #{})
disconnected (deref disconnected*)
on-change-disconnected
(mf/use-fn
(fn [id]
(swap! disconnected*
(fn [s]
(if (contains? s id) (disj s id) (conj s id))))))]
;; Pre-select first candidate for each library ;; Pre-select first candidate for each library
(mf/with-effect [candidates] (mf/with-effect [candidates]
@ -407,21 +416,53 @@
:typography t/body-large} :typography t/body-large}
"Some libraries couldn't be linked automatically. Select the correct library for each:"] "Some libraries couldn't be linked automatically. Select the correct library for each:"]
[:table {:class (stl/css :library-resolution-table)}
[:thead
[:tr {:class (stl/css :library-resolution-header)}
[:th {:class (stl/css :library-origin-name)}
[:> icon* {:icon-id i/library
:class (stl/css :library-resolution-icon)
:size "s"}]
"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"]]]
[:tbody {:class (stl/css :library-resolution-body)}
(for [{:keys [id name candidates]} candidates] (for [{:keys [id name candidates]} candidates]
(let [options (mapv (fn [c] (let [options (mapv (fn [c]
{:id (str (:id c)) {:id (str (:id c))
:label (str (:name c) " (" (:project-name c) ")")}) :label (str (:name c) " (" (:project-name c) ")")})
candidates) candidates)
selected (get selection id)] selected (get selection id)
[:div {:class (stl/css :library-resolution-item) is-conected (not (contains? disconnected id))]
[:tr {:class (stl/css :library-resolution-item)
:key (dm/str id)} :key (dm/str id)}
[:div {:class (stl/css :library-resolution-item-name)} [:td {:class (stl/css :library-resolution-item-name)}
name] [:> checkbox* {:id (dm/str id)
:label name
:checked is-conected
:on-change #(on-change-disconnected id)}]]
[:td {:class (stl/css :library-resolution-arrow)}
[:> icon* {:icon-id i/row
:size "m"}]]
[:td
(if is-conected
[:> select* {:options options [:> select* {:options options
:class (stl/css :library-resolution-select) :class (stl/css :library-resolution-select)
:default-selected (or (some-> selected str) "") :default-selected (or (some-> selected str) "")
:has-portal true :has-portal true
:on-change (partial on-select id)}]]))])) :on-change (partial on-select id)}]
[:> text* {:class (stl/css :library-resolution-no-selection)
:as "span"
:typography t/body-small}
(let [selected-c (or (some #(when (= (str (:id %)) selected) %) candidates)
(first candidates))]
(dm/str (:name selected-c) " (" (:project-name selected-c) ")"))])]]))]]]))
(mf/defc library-resolution-summary-file* (mf/defc library-resolution-summary-file*
{::mf/private true} {::mf/private true}
@ -430,19 +471,16 @@
pending (:pending resolution-file)] pending (:pending resolution-file)]
[:div {:class (stl/css :summary-file)} [:div {:class (stl/css :summary-file)}
[:div {:class (stl/css :summary-file-header)} [:div {:class (stl/css :summary-file-header)}
[:> icon* {:icon-id i/library [:> icon* {:icon-id i/document
:class (stl/css :summary-file-icon) :class (stl/css :summary-file-icon)
:size "s"}] :size "s"}]
[:span {:class (stl/css :summary-file-name)} [:> text* {:class (stl/css :summary-file-name)
:as "span"
:typography t/body-medium}
(:name resolution-file)]] (:name resolution-file)]]
(when (seq done) (when (seq done)
[:div {:class (stl/css :summary-section)} [:div {:class (stl/css :summary-section)}
[:div {:class (stl/css :summary-section-header)}
[:> icon* {:icon-id i/status-tick
:class (stl/css :summary-section-icon)
:size "s"}]
[:span (tr "dashboard.import.summary.auto-linked")]]
[:ul {:class (stl/css :summary-list)} [:ul {:class (stl/css :summary-list)}
(for [{:keys [name]} done] (for [{:keys [name]} done]
[:li {:class (stl/css :summary-list-item) [:li {:class (stl/css :summary-list-item)
@ -457,11 +495,17 @@
(when (seq pending) (when (seq pending)
[:div {:class (stl/css :summary-section)} [:div {:class (stl/css :summary-section)}
[:div {:class (stl/css :summary-section-header)} [:div {:class (stl/css :summary-section-header)}
[:> icon* {:icon-id i/open-link [:> text* {:as "span"
:class (stl/css :summary-section-icon) :typography t/headline-small}
:size "s"}] "linked manually"]]
[:span (tr "dashboard.import.summary.your-selection")]]
[:ul {:class (stl/css :summary-list)} [: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)}
"New"]]
(for [{:keys [id name] :as cand} pending] (for [{:keys [id name] :as cand} pending]
(let [selected-id (get selection id) (let [selected-id (get selection id)
selected-c (when selected-id selected-c (when selected-id
@ -469,6 +513,9 @@
[:li {:class (stl/css :summary-list-item) [:li {:class (stl/css :summary-list-item)
:key (dm/str id)} :key (dm/str id)}
[:span {:class (stl/css :summary-item-name)} name] [:span {:class (stl/css :summary-item-name)} name]
[:> icon* {:icon-id i/row
:size "m"
:class (stl/css :summary-linked-arrow)}]
(if selected-c (if selected-c
[:span {:class (stl/css :summary-linked-info)} [:span {:class (stl/css :summary-linked-info)}
[:span {:class (stl/css :summary-linked-name)} [:span {:class (stl/css :summary-linked-name)}
@ -835,50 +882,60 @@
(= :library-resolution status) (= :library-resolution status)
[:* [:*
(when (seq visited) (when (seq visited)
[:input {:class (stl/css :cancel-button) [:> button* {:class (stl/css :cancel-button)
:type "button" :variant "secondary"
:value (tr "labels.previous") :on-click on-wizard-prev}
:on-click on-wizard-prev}]) (tr "labels.previous")])
;; Label flips to "Review" when this is the last unvisited unresolved file. ;; Label flips to "Review" when this is the last unvisited unresolved file.
[:input {:class (stl/css :accept-btn) [:> button* {:class (stl/css :accept-btn)
:type "button" :variant "primary"
:value (if all-visited? :on-click on-wizard-next}
(if all-visited?
(tr "labels.next") (tr "labels.next")
(tr "dashboard.import.review-links")) (tr "dashboard.import.review-links"))]]
:on-click on-wizard-next}]]
;; Summary: Confirm / Back ;; Summary: Confirm / Back
;; Back pops the stack once and re-enters the wizard at the popped file. ;; Back pops the stack once and re-enters the wizard at the popped file.
(= :library-summary status) (= :library-summary status)
[:* [:*
(when (seq visited) (when (seq visited)
[:input {:class (stl/css :cancel-button) [:> button* {:class (stl/css :cancel-button)
:type "button" :variant "secondary"
:value (tr "labels.back") :on-click on-summary-back}
:on-click on-summary-back}]) (tr "labels.back")])
[:input {:class (stl/css :accept-btn) [:> button* {:class (stl/css :accept-btn)
:type "button" :variant "primary"
:value (tr "dashboard.import.confirm-library-links") :on-click on-confirm-library-links}
:on-click on-confirm-library-links}]] (tr "dashboard.import.confirm-library-links")]]
(= :analyze status) (= :analyze status)
[:input {:class (stl/css :cancel-button) [:> button* {:class (stl/css :cancel-button)
:type "button" :variant "secondary"
:value (tr "labels.cancel") :on-click on-cancel}
:on-click on-cancel}] (tr "labels.cancel")]
(= status :import-error)
[:> button* {:class (stl/css :accept-btn)
:variant "primary"
:on-click on-accept}
(tr "labels.accept")]
(= status :import-success)
[:> button* {:class (stl/css :accept-btn)
:variant "primary"
:on-click on-accept}
(tr "labels.accept")]
(= status :import-ready) (= status :import-ready)
[:input {:class (stl/css :accept-btn) [:> button* {:class (stl/css :accept-btn)
:type "button" :variant "primary"
:value (tr "labels.continue") :on-click on-continue}
:disabled pending-analysis? (tr "labels.continue")]
:on-click on-continue}]
(or (= :import-success status) (or (= :import-success status)
(= :import-error status) (= :import-error status)
(= :import-progress status)) (= :import-progress status))
[:input {:class (stl/css :accept-btn) [:> button* {:class (stl/css :accept-btn)
:type "button" :variant "primary"
:value (tr "labels.accept") :on-click on-accept}
:disabled (= :import-progress status) (tr "labels.accept")])]]]]))
:on-click on-accept}])]]]]))

View File

@ -35,66 +35,10 @@
border: $b-2 solid var(--color-background-quaternary); border: $b-2 solid var(--color-background-quaternary);
min-block-size: $sz-192; min-block-size: $sz-192;
inline-size: $sz-512; inline-size: $sz-512;
max-block-size: calc(10 * px2rem(80)); max-block-size: px2rem(800);
} }
.modal-content { .modal-content {
display: flex;
flex-direction: column;
gap: var(--sp-l);
margin-block-end: var(--sp-xxl);
}
.modal-title {
color: var(--color-foreground-primary);
}
.modal-close-btn {
// %button-tertiary essentials
border-radius: $br-8;
background-color: transparent;
border: $b-2 solid transparent;
color: var(--button-tertiary-foreground-color-rest);
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
inset-block-start: var(--sp-s);
inset-inline-end: px2rem(6);
block-size: $sz-32;
inline-size: $sz-28;
svg {
display: flex;
justify-content: center;
align-items: center;
block-size: $sz-16;
inline-size: $sz-16;
color: transparent;
fill: none;
stroke-width: 1px;
stroke: var(--button-tertiary-foreground-color-rest);
}
&:hover {
background-color: var(--button-tertiary-background-color-hover);
color: var(--button-tertiary-foreground-color-hover);
border-color: var(--button-secondary-border-color-hover);
svg {
stroke: var(--button-tertiary-foreground-color-hover);
}
}
}
.modal-content {
// TODO: Typography does not match any existing DS typography token.
// body-small-typography: line-height 1.4 (DS body-small has line-height 1.3)
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(12);
font-weight: 400;
line-height: 1.4;
flex: 1; flex: 1;
overflow: hidden auto; overflow: hidden auto;
display: grid; display: grid;
@ -104,13 +48,19 @@
min-block-size: px2rem(40); min-block-size: px2rem(40);
} }
.modal-title {
color: var(--color-foreground-primary);
}
.modal-close-btn {
position: absolute;
inset-block-start: var(--sp-s);
inset-inline-end: px2rem(6);
}
.status-message { .status-message {
// TODO: Typography does not match any existing DS typography token. @include use-typography("body-small");
// body-small-typography: line-height 1.4 (DS body-small has line-height 1.3)
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(12);
font-weight: 400;
line-height: 1.4;
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
font-style: italic; font-style: italic;
} }
@ -121,105 +71,6 @@
gap: var(--sp-l); gap: var(--sp-l);
} }
.cancel-button {
// %modal-cancel-btn essentials (these are <input type=button> elements)
border-radius: $br-8;
background-color: var(--button-secondary-background-color-rest);
border: $b-1 solid var(--button-secondary-border-color-rest);
color: var(--button-secondary-foreground-color-rest);
display: flex;
justify-content: center;
align-items: center;
padding: var(--sp-s) var(--sp-xxl);
block-size: $sz-32;
margin: 0;
cursor: pointer;
// TODO: Typography does not match any existing DS typography token.
// uppercase-title-typography: 11px, weight 500, line-height 1.2, uppercase
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(11);
font-weight: 500;
line-height: 1.2;
text-transform: uppercase;
&:hover {
background-color: var(--button-secondary-background-color-hover);
border-color: var(--button-secondary-border-color-hover);
color: var(--button-secondary-foreground-color-hover);
}
&:active {
background-color: var(--button-secondary-background-color-active);
border-color: var(--button-secondary-border-color-active);
color: var(--button-secondary-foreground-color-active);
}
&[disabled],
&:disabled {
background-color: var(--button-background-color-disabled);
border-color: var(--button-border-color-disabled);
color: var(--button-foreground-color-disabled);
cursor: unset;
}
}
.accept-btn {
// %modal-accept-btn essentials
border-radius: $br-8;
background-color: var(--button-primary-background-color-rest);
border: $b-1 solid var(--button-primary-border-color-rest);
color: var(--button-primary-foreground-color-rest);
display: flex;
justify-content: center;
align-items: center;
padding: var(--sp-s) var(--sp-xxl);
block-size: $sz-32;
margin: 0;
cursor: pointer;
// TODO: Typography does not match any existing DS typography token.
// uppercase-title-typography: 11px, weight 500, line-height 1.2, uppercase
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(11);
font-weight: 500;
line-height: 1.2;
text-transform: uppercase;
&:hover {
background-color: var(--button-primary-background-color-hover);
border-color: var(--button-primary-border-color-hover);
color: var(--button-primary-foreground-color-hover);
}
&:active {
background-color: var(--button-primary-background-color-active);
border-color: var(--button-primary-border-color-active);
color: var(--button-primary-foreground-color-active);
}
&[disabled],
&:disabled {
background-color: var(--button-background-color-disabled);
border-color: var(--button-border-color-disabled);
color: var(--button-foreground-color-disabled);
cursor: unset;
}
&.danger {
background-color: var(--modal-button-background-color-error);
border-color: var(--modal-button-background-color-error);
color: var(--modal-button-foreground-color-error);
}
}
// TODO: Verify before removal. These selectors appear to be unused.
// .modal-scd-msg,
// .modal-subtitle,
// .modal-msg {
// ...
// }
.file-entry { .file-entry {
--file-entry-fg-color: var(--color-foreground-secondary); --file-entry-fg-color: var(--color-foreground-secondary);
@ -227,6 +78,19 @@
flex-direction: column; flex-direction: column;
gap: var(--sp-m); gap: var(--sp-m);
&.success {
--file-entry-fg-color: var(--color-accent-sucess);
}
&.error {
--file-entry-fg-color: var(--color-accent-error);
}
&.editable {
--file-entry-fg-color: var(--color-foreground-primary);
}
}
.error-message, .error-message,
.progress-message { .progress-message {
display: flex; display: flex;
@ -246,6 +110,7 @@
align-items: center; align-items: center;
gap: var(--sp-m); gap: var(--sp-m);
color: var(--file-entry-fg-color); color: var(--file-entry-fg-color);
}
.linked-library-tag { .linked-library-tag {
display: flex; display: flex;
@ -272,24 +137,6 @@
} }
} }
} }
}
&.warning {
--file-entry-fg-color: var(--color-accent-warning);
}
&.success {
--file-entry-fg-color: var(--color-accent-sucess);
}
&.error {
--file-entry-fg-color: var(--color-accent-error);
}
&.editable {
--file-entry-fg-color: var(--color-foreground-primary);
}
}
.file-name { .file-name {
display: flex; display: flex;
@ -302,19 +149,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--sp-xs); gap: var(--sp-xs);
// button {
// display: flex;
// justify-content: center;
// align-items: center;
// border-radius: $br-8;
// background-color: transparent;
// border: $b-2 solid transparent;
// inline-size: $sz-28;
// block-size: $sz-32;
// cursor: pointer;
// color: var(--button-tertiary-foreground-color-rest);
// }
} }
.file-name-edit { .file-name-edit {
@ -329,14 +163,14 @@
background-color: var(--color-background-tertiary); background-color: var(--color-background-tertiary);
} }
.file-name-label { .file-name-label {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--sp-m); gap: var(--sp-m);
flex-grow: 1; flex-grow: 1;
}
.icon { .file-label-icon {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -344,9 +178,6 @@
inline-size: $sz-16; inline-size: $sz-16;
color: var(--file-entry-fg-color); color: var(--file-entry-fg-color);
} }
}
.file-name-input { .file-name-input {
@include use-typography("body-medium"); @include use-typography("body-medium");
@ -371,25 +202,6 @@
} }
} }
.file-icon,
.file-svg {
display: flex;
justify-content: center;
align-items: center;
block-size: $sz-24;
inline-size: $sz-16;
color: var(--file-entry-fg-color);
}
.file-svg {
fill: var(--file-entry-fg-color);
}
.context-notification-error {
--context-notification-bg-color: var(--color-background-primary);
}
.file-name-notification { .file-name-notification {
flex-grow: 1; flex-grow: 1;
} }
@ -419,6 +231,10 @@
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
// ################################
// LIBRARY RESOLUTION
// ################################
.library-resolution { .library-resolution {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -430,50 +246,96 @@
margin-block-end: var(--sp-s); margin-block-end: var(--sp-s);
} }
.library-resolution-header {
@include use-typography("body-medium");
color: var(--color-foreground-secondary);
display: grid;
grid-template-columns: 1fr 32px 1fr;
border-block-end: $b-1 solid var(--color-foreground-secondary);
}
.library-origin-name,
.library-resolution-connection {
block-size: $sz-32;
text-align: start;
color: var(--color-foreground-primary);
display: flex;
align-items: center;
}
.library-resolution-item { .library-resolution-item {
display: grid;
grid-template-columns: 1fr 32px 1fr;
block-size: $sz-32;
margin-block: var(--sp-s);
}
.library-resolution-icon {
display: flex;
justify-content: center;
align-items: center;
margin-inline-end: var(--sp-s);
color: var(--color-foreground-secondary);
}
.library-resolution-arrow {
color: var(--color-foreground-secondary);
display: flex;
justify-content: center;
align-items: center;
min-block-size: $sz-32;
}
.library-resolution-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--sp-xs); gap: var(--sp-s);
padding: var(--sp-s) 0;
} }
.library-resolution-item-name { .library-resolution-item-name {
// TODO: Typography does not match any existing DS typography token. @include use-typography("body-medium");
font-family: "worksans", "vazirmatn", sans-serif;
font-size: px2rem(12);
line-height: 1.4;
color: var(--color-foreground-primary);
font-weight: 700;
}
.library-resolution-summary-file { display: flex;
margin-block-end: var(--sp-l); align-items: center;
padding-block-end: var(--sp-m); justify-content: space-between;
border-bottom: $b-1 solid var(--color-foreground-secondary); gap: var(--sp-s);
}
.library-resolution-linked {
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
font-size: px2rem(12); padding-inline-start: var(--sp-s);
} }
.library-resolution-project { .library-resolution-no-selection {
display: flex;
align-items: center;
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
font-size: px2rem(12); padding: var(--sp-s);
margin-block-end: var(--sp-s); margin-inline-start: var(--sp-xxs);
block-size: $sz-32;
} }
// ################################
// Summary file card // Summary file card
// ################################
.summary-file { .summary-file {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--sp-s); gap: var(--sp-s);
padding: var(--sp-m); padding: var(--sp-m);
border: $b-1 solid var(--color-background-quaternary);
border-radius: $br-8; border-radius: $br-8;
background: var(--color-background-primary); background: var(--color-background-primary);
} }
.summary-list {
display: flex;
flex-direction: column;
gap: var(--sp-xxs);
list-style: none;
padding: 0;
margin: 0;
border-inline-start: $b-1 solid var(--color-background-quaternary);
}
.summary-file-header { .summary-file-header {
display: flex; display: flex;
align-items: center; align-items: center;
@ -488,10 +350,7 @@
} }
.summary-file-name { .summary-file-name {
@include use-typography("body-medium");
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
font-weight: 700;
} }
// Section within a file (auto-linked or user selection) // Section within a file (auto-linked or user selection)
@ -509,20 +368,6 @@
padding: var(--sp-xs) 0; padding: var(--sp-xs) 0;
} }
.summary-section-icon {
color: var(--color-foreground-secondary);
flex-shrink: 0;
}
.summary-list {
display: flex;
flex-direction: column;
gap: var(--sp-xxs);
list-style: none;
padding: 0;
margin: 0;
}
.summary-list-item { .summary-list-item {
display: flex; display: flex;
align-items: center; align-items: center;
@ -537,11 +382,6 @@
} }
.summary-item-name { .summary-item-name {
// 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;
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
flex: 1; flex: 1;
min-inline-size: 0; min-inline-size: 0;
@ -552,6 +392,17 @@
white-space: nowrap; white-space: nowrap;
} }
.summary-item-name-header {
color: var(--color-foreground-secondary);
flex: 1;
min-inline-size: 0;
display: block;
max-inline-size: 99%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Auto-linked badge // Auto-linked badge
.summary-linked-badge { .summary-linked-badge {
display: inline-flex; display: inline-flex;