diff --git a/.gitignore b/.gitignore index 105d3897a3..224d199dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ /frontend/resources/public/* /frontend/storybook-static/ /frontend/target/ +/frontend/test-results/ /other/ /scripts/ /telemetry/ diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 7f0034c0b0..00c5ecf1ba 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -474,6 +474,8 @@ :height #{:sizing :dimensions} :max-width #{:sizing :dimensions} :max-height #{:sizing :dimensions} + :min-width #{:sizing :dimensions} + :min-height #{:sizing :dimensions} :x #{:dimensions} :y #{:dimensions} :rotation #{:number :rotation} diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index d1dc297ca2..7f92c424dc 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -759,4 +759,87 @@ test.describe("Tokens: Apply token", () => { }); await expect(StrokeWidthPillSmall).toBeVisible(); }); + + test("User applies margin token to a shape", async ({ page }) => { + const workspace = new WorkspacePage(page, { + textEditor: true, + }); + // Set up + await workspace.mockConfigFlags(["enable-feature-token-input"]); + await workspace.setupEmptyFile(); + await workspace.mockGetFile("workspace/get-file-layout-stroke-token-json"); + await workspace.goToWorkspace(); + + // Select shape apply stroke + await workspace.layers + .getByTestId("layer-row") + .nth(1) + .getByRole("button", { name: "Toggle layer" }) + .click(); + + await workspace.layers.getByTestId("layer-row").nth(2).click(); + + const rightSidebar = page.getByTestId("right-sidebar"); + await expect(rightSidebar).toBeVisible(); + await rightSidebar.getByTestId("add-stroke").click(); + + // Apply margin token from token panel + const tokensTab = page.getByRole("tab", { name: "Tokens" }); + await expect(tokensTab).toBeVisible(); + await tokensTab.click(); + await page.getByRole("button", { name: "Dimensions 4" }).click(); + await page.getByRole("button", { name: "dim", exact: true }).click(); + const tokensSidebar = workspace.tokensSidebar; + await expect( + tokensSidebar.getByRole("button", { name: "dim.md" }), + ).toBeVisible(); + await tokensSidebar + .getByRole("button", { name: "dim.md" }) + .click({ button: "right" }); + await page + .getByTestId("tokens-context-menu-for-token") + .getByText("Spacing") + .hover(); + await page + .getByTestId("tokens-context-menu-for-token") + .getByText("Horizontal") + .click(); + + // Check if token pill is visible on right sidebar + const layoutItemSectionSidebar = rightSidebar.getByRole("region", { + name: "layout item menu", + }); + await expect(layoutItemSectionSidebar).toBeVisible(); + const marginPillMd = layoutItemSectionSidebar.getByRole("button", { + name: "dim.md", + }); + await expect(marginPillMd).toBeVisible(); + + await marginPillMd.click(); + const dimensionTokenOptionXl = page.getByRole("option", { name: "dim.xl" }); + await expect(dimensionTokenOptionXl).toBeVisible(); + await dimensionTokenOptionXl.click(); + + const marginPillXL = layoutItemSectionSidebar.getByRole("button", { + name: "dim.xl", + }); + await expect(marginPillXL).toBeVisible(); + + // Detach token from right sidebar and apply another from dropdown + const detachButton = layoutItemSectionSidebar.getByRole("button", { + name: "Detach token", + }); + await detachButton.click(); + await expect(marginPillXL).not.toBeVisible(); + const horizontalMarginInput = layoutItemSectionSidebar.getByText('Horizontal marginOpen token'); + await expect(horizontalMarginInput).toBeVisible(); + + const tokenDropdown = horizontalMarginInput.getByRole('button', { name: 'Open token list' }); + await tokenDropdown.click(); + + await expect(dimensionTokenOptionXl).toBeVisible(); + await dimensionTokenOptionXl.click(); + + await expect(marginPillXL).toBeVisible(); + }); }); diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index e6dbfb4efd..7243951d34 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -539,10 +539,11 @@ value shape-ids #{:stroke-width} page-id)) - (some attributes #{:max-width :max-height}) + + (some attributes #{:max-width :max-height :layout-item-max-h :layout-item-max-w :layout-item-min-h :layout-item-min-w}) (conj #(update-layout-sizing-limits value shape-ids - (set (filter attributes #{:max-width :max-height})) + (set (filter attributes #{:max-width :max-height :layout-item-max-h :layout-item-max-w :layout-item-min-h :layout-item-min-w})) page-id)))) (defn apply-dimensions-token diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.cljs b/frontend/src/app/main/ui/ds/controls/numeric_input.cljs index d7dbf94de3..f9584fba69 100644 --- a/frontend/src/app/main/ui/ds/controls/numeric_input.cljs +++ b/frontend/src/app/main/ui/ds/controls/numeric_input.cljs @@ -189,6 +189,7 @@ :float :string [:= :multiple]]]] + [:text-icon {:optional true} :string] [:default {:optional true} [:maybe :string]] [:placeholder {:optional true} :string] [:icon {:optional true} [:maybe schema:icon]] @@ -216,7 +217,8 @@ is-selected-on-focus nillable tokens applied-token empty-to-end on-change on-blur on-focus on-detach - property align ref name] + property align ref name + text-icon] :rest props}] (let [;; NOTE: we use mfu/bean here for transparently handle @@ -637,14 +639,23 @@ :on-change store-raw-value :variant "comfortable" :disabled disabled - :slot-start (when icon - (mf/html [:> tooltip* - {:content property - :id property} - [:> icon* {:icon-id icon - :size "s" - :aria-labelledby property - :class (stl/css :icon)}]])) + :slot-start (when (or icon text-icon) + (mf/html + [:> tooltip* + {:content property + :id property} + (cond + icon + [:> icon* + {:icon-id icon + :size "s" + :aria-labelledby property + :class (stl/css :icon)}] + + text-icon + [:div {:class (stl/css :text-icon) + :aria-labelledby property} + text-icon])])) :slot-end (when-not disabled (when (some? tokens) (mf/html [:> icon-button* {:variant "ghost" @@ -676,14 +687,23 @@ :disabled disabled :on-blur on-blur :class inner-class - :slot-start (when icon - (mf/html [:> tooltip* - {:content property - :id property} - [:> icon* {:icon-id icon - :size "s" - :aria-labelledby property - :class (stl/css :icon)}]])) + :slot-start (when (or icon text-icon) + (mf/html + [:> tooltip* + {:content property + :id property} + (cond + icon + [:> icon* + {:icon-id icon + :size "s" + :aria-labelledby property + :class (stl/css :icon)}] + + text-icon + [:div {:class (stl/css :text-icon) + :aria-labelledby property} + text-icon])])) :token-wrapper-ref token-wrapper-ref :token-detach-btn-ref token-detach-btn-ref :detach-token detach-token})))] @@ -718,21 +738,40 @@ (mf/with-effect [dropdown-options] (mf/set-ref-val! options-ref dropdown-options)) - [:div {:class (dm/str class " " (stl/css :input-wrapper)) - :ref wrapper-ref} + (if (some? icon) + [:div {:class [class (stl/css :input-wrapper)] + :ref wrapper-ref} - (if (and (some? token-applied) - (not= :multiple token-applied)) - [:> token-field* token-props] - [:> input-field* input-props]) + (if (and (some? token-applied) + (not= :multiple token-applied)) + [:> token-field* token-props] + [:> input-field* input-props]) - (when ^boolean is-open - (let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)] - [:> options-dropdown* {:on-click on-option-click - :id listbox-id - :options options - :selected selected-id - :focused focused-id - :align align - :empty-to-end empty-to-end - :ref set-option-ref}]))])) + (when ^boolean is-open + (let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)] + [:> options-dropdown* {:on-click on-option-click + :id listbox-id + :options options + :selected selected-id + :focused focused-id + :align align + :empty-to-end empty-to-end + :ref set-option-ref}]))] + [:div {:class [class (stl/css :input-wrapper)] + :ref wrapper-ref} + + (if (and (some? token-applied) + (not= :multiple token-applied)) + [:> token-field* token-props] + [:> input-field* input-props]) + + (when ^boolean is-open + (let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)] + [:> options-dropdown* {:on-click on-option-click + :id listbox-id + :options options + :selected selected-id + :focused focused-id + :align align + :empty-to-end empty-to-end + :ref set-option-ref}]))]))) diff --git a/frontend/src/app/main/ui/ds/controls/numeric_input.scss b/frontend/src/app/main/ui/ds/controls/numeric_input.scss index 45a440b62d..b3c7506529 100644 --- a/frontend/src/app/main/ui/ds/controls/numeric_input.scss +++ b/frontend/src/app/main/ui/ds/controls/numeric_input.scss @@ -8,6 +8,7 @@ @use "ds/spacing.scss" as *; @use "ds/_sizes.scss" as *; @use "ds/typography.scss" as t; +@use "ds/_utils.scss" as *; .input-wrapper { --input-padding-size: var(--sp-xs); @@ -29,7 +30,14 @@ .icon { color: var(--color-foreground-secondary); - min-width: var(--sp-l); + min-inline-size: var(--sp-l); +} + +.text-icon { + color: var(--color-foreground-secondary); + @include t.use-typography("code-font"); + inline-size: fit-content; + min-inline-size: px2rem(40); } .invisible-button { diff --git a/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss b/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss index a4ff7b8acc..14c32957ba 100644 --- a/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss +++ b/frontend/src/app/main/ui/ds/controls/utilities/token_field.scss @@ -17,7 +17,7 @@ --token-field-outline-color: none; --token-field-height: var(--sp-xxxl); --token-field-margin: unset; - display: grid; + display: inline-flex; column-gap: var(--sp-xs); align-items: center; position: relative; diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs index d374754e39..62ce0296a6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.cljs @@ -119,6 +119,7 @@ [:button {:class (stl/css-case :toggle-content true :inverse expanded?) + :aria-label "Toggle layer" :on-click on-toggle-collapse} deprecated-icon/arrow]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 6b8935bd72..64f3868282 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -8,14 +8,20 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.common.schema :as sm] [app.common.types.shape.layout :as ctl] + [app.common.types.token :as tk] [app.main.data.workspace :as udw] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.tokens.application :as dwta] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.numeric-input :as deprecated-input] [app.main.ui.components.title-bar :refer [title-bar*]] + [app.main.ui.context :as muc] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.numeric-input :refer [numeric-input*]] [app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]] [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.icons :as deprecated-icon] @@ -24,6 +30,51 @@ [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) +(mf/defc numeric-input-wrapper* + {::mf/private true} + [{:keys [values name applied-tokens align on-detach placeholder] :rest props}] + (let [tokens (mf/use-ctx muc/active-tokens-by-type) + input-type (cond + (some #{:m2 :m4} [name]) + :horizontal-margin + + (some #{:m1 :m3} [name]) + :vertical-margin + + (= name :layout-item-max-w) + :max-width + + (= name :layout-item-max-h) + :max-height + + (= name :layout-item-min-w) + :min-width + + (= name :layout-item-min-h) + :min-height + + :else + name) + + tokens (mf/with-memo [tokens input-type] + (delay + (-> (deref tokens) + (select-keys (get tk/tokens-by-input input-type)) + (not-empty)))) + on-detach-attr + (mf/use-fn + (mf/deps on-detach name) + #(on-detach % name)) + + props (mf/spread-props props + {:placeholder (or placeholder "--") + :applied-token (get applied-tokens name) + :tokens tokens + :align align + :on-detach on-detach-attr + :value (get values name)})] + [:> numeric-input* props])) + (def layout-item-attrs [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} :layout-item-margin-type ;; :simple :multiple @@ -46,150 +97,345 @@ (select-margins (= prop :m1) (= prop :m2) (= prop :m3) (= prop :m4))) (mf/defc margin-simple* - [{:keys [value on-change on-blur]}] - (let [m1 (:m1 value) + [{:keys [value on-change on-blur applied-tokens ids]}] + (let [token-numeric-inputs + (features/use-feature "tokens/numeric-input") + + m1 (:m1 value) m2 (:m2 value) m3 (:m3 value) m4 (:m4 value) - m1-placeholder (if (and (not= value :multiple) (not= m1 m3)) (tr "settings.multiple") "--") - m2-placeholder (if (and (not= value :multiple) (not= m2 m4)) (tr "settings.multiple") "--") - m1 (when (and (not= value :multiple) (= m1 m3)) m1) m2 (when (and (not= value :multiple) (= m2 m4)) m2) + token-applied-m1 (:m1 applied-tokens) + token-applied-m2 (:m2 applied-tokens) + token-applied-m3 (:m3 applied-tokens) + token-applied-m4 (:m4 applied-tokens) + + token-applied-m1 (if (and (not= applied-tokens :multiple) (= token-applied-m1 token-applied-m3)) token-applied-m1 + :multiple) + + token-applied-m2 (if (and (not= applied-tokens :multiple) (= token-applied-m2 token-applied-m4)) token-applied-m2 + :multiple) + + m1-placeholder (if (and (not= value :multiple) + (= m1 m3) + (= token-applied-m1 token-applied-m3)) + "--" + (tr "settings.multiple")) + + m2-placeholder (if (and (not= value :multiple) + (= m2 m4) + (= token-applied-m2 token-applied-m4)) + "--" + (tr "settings.multiple")) + on-focus (mf/use-fn - (fn [event] - (let [attr (-> (dom/get-current-target event) - (dom/get-data "name") - (keyword))] - (case attr - :m1 (select-margins true false true false) - :m2 (select-margins false true false true)) + (mf/deps select-margins) + (fn [attr event] + (case attr + :m1 (select-margins true false true false) + :m2 (select-margins false true false true)) + (dom/select-target event))) - (dom/select-target event)))) + on-detach-token + (mf/use-fn + (mf/deps ids) + (fn [token attr] + (st/emit! (dwta/unapply-token {:token (first token) + :attributes #{attr} + :shape-ids ids})))) + + on-detach-horizontal + (mf/use-fn + (mf/deps on-detach-token) + (fn [token] + (run! #(on-detach-token token %) [:m2 :m4]))) + + on-detach-vertical + (mf/use-fn + (mf/deps on-detach-token) + (fn [token] + (run! #(on-detach-token token %) [:m1 :m3]))) on-change' (mf/use-fn - (mf/deps on-change) - (fn [value event] - (let [attr (-> (dom/get-current-target event) - (dom/get-data "name") - (keyword))] - (on-change :simple attr value))))] + (mf/deps on-change ids) + (fn [value attr] + (if (or (string? value) (int? value)) + (on-change :simple attr value) + (do + (st/emit! + (dwta/toggle-token {:token (first value) + :attrs (if (= :m1 attr) + #{:m1 :m3} + #{:m2 :m4}) + :shape-ids ids})))))) + + on-focus-m1 + (mf/use-fn (mf/deps on-focus) #(on-focus :m1)) + + on-focus-m2 + (mf/use-fn (mf/deps on-focus) #(on-focus :m2)) + + on-m1-change + (mf/use-fn (mf/deps on-change') #(on-change' % :m1)) + + on-m2-change + (mf/use-fn (mf/deps on-change') #(on-change' % :m2))] [:div {:class (stl/css :margin-simple)} - [:div {:class (stl/css :vertical-margin) - :title "Vertical margin"} - [:span {:class (stl/css :icon)} - deprecated-icon/margin-top-bottom] - [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) - :placeholder m1-placeholder - :data-name "m1" - :on-focus on-focus - :on-change on-change' - :on-blur on-blur - :nillable true - :value m1}]] + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-m1-change + :on-detach on-detach-vertical + :class (stl/css :vertical-margin-wrapper) + :on-blur on-blur + :on-focus on-focus-m1 + :placeholder m1-placeholder + :icon i/margin-top-bottom + :min 0 + :name :m1 + :property "Vertical margin " + :nillable true + :applied-tokens {:m1 token-applied-m1} + :values {:m1 m1}}] - [:div {:class (stl/css :horizontal-margin) - :title "Horizontal margin"} - [:span {:class (stl/css :icon)} - deprecated-icon/margin-left-right] - [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) - :placeholder m2-placeholder - :data-name "m2" - :on-focus on-focus - :on-change on-change' - :on-blur on-blur - :nillable true - :value m2}]]])) + [:div {:class (stl/css :vertical-margin) + :title "Vertical margin"} + [:span {:class (stl/css :icon)} + deprecated-icon/margin-top-bottom] + [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) + :placeholder m1-placeholder + :data-name "m1" + :on-focus on-focus-m1 + :on-change on-m1-change + :on-blur on-blur + :nillable true + :value m1}]]) + + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-m2-change + :on-detach on-detach-horizontal + :on-blur on-blur + :on-focus on-focus-m2 + :placeholder m2-placeholder + :icon i/margin-left-right + :class (stl/css :horizontal-margin-wrapper) + :min 0 + :name :m2 + :align :right + :property "Horizontal margin" + :nillable true + :applied-tokens {:m2 token-applied-m2} + :values {:m2 m2}}] + + [:div {:class (stl/css :horizontal-margin) + :title "Horizontal margin"} + [:span {:class (stl/css :icon)} + deprecated-icon/margin-left-right] + [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) + :placeholder m2-placeholder + :data-name "m2" + :on-focus on-focus-m2 + :on-change on-m2-change + :on-blur on-blur + :nillable true + :value m2}]])])) (mf/defc margin-multiple* - [{:keys [value on-change on-blur]}] - (let [m1 (:m1 value) + [{:keys [value on-change on-blur applied-tokens ids]}] + (let [token-numeric-inputs + (features/use-feature "tokens/numeric-input") + + m1 (:m1 value) m2 (:m2 value) m3 (:m3 value) m4 (:m4 value) + applied-token-to-m1 (:m1 applied-tokens) + applied-token-to-m2 (:m2 applied-tokens) + applied-token-to-m3 (:m3 applied-tokens) + applied-token-to-m4 (:m4 applied-tokens) + + on-detach-token + (mf/use-fn + (mf/deps ids) + (fn [token attr] + (st/emit! (dwta/unapply-token {:token (first token) + :attributes #{attr} + :shape-ids ids})))) + on-focus (mf/use-fn - (fn [event] - (let [attr (-> (dom/get-current-target event) - (dom/get-data "name") - (keyword))] - (select-margin attr) - (dom/select-target event)))) + (mf/deps select-margin) + (fn [attr event] + (select-margin attr) + (dom/select-target event))) + + on-focus-m1 + (mf/use-fn (mf/deps on-focus) #(on-focus :m1)) + + on-focus-m2 + (mf/use-fn (mf/deps on-focus) #(on-focus :m2)) + + on-focus-m3 + (mf/use-fn (mf/deps on-focus) #(on-focus :m1)) + + on-focus-m4 + (mf/use-fn (mf/deps on-focus) #(on-focus :m2)) on-change' (mf/use-fn - (mf/deps on-change) - (fn [value event] - (let [attr (-> (dom/get-current-target event) - (dom/get-data "name") - (keyword))] - (on-change :multiple attr value))))] + (mf/deps on-change ids) + (fn [value attr] + (if (or (string? value) (int? value)) + (on-change :multiple attr value) + (do + (st/emit! + (dwta/toggle-token {:token (first value) + :attrs #{attr} + :shape-ids ids})))))) + + + on-m1-change + (mf/use-fn (mf/deps on-change') #(on-change' % :m1)) + + on-m2-change + (mf/use-fn (mf/deps on-change') #(on-change' % :m2)) + + on-m3-change + (mf/use-fn (mf/deps on-change') #(on-change' % :m3)) + + on-m4-change + (mf/use-fn (mf/deps on-change') #(on-change' % :m4))] [:div {:class (stl/css :margin-multiple)} - [:div {:class (stl/css :top-margin) - :title "Top margin"} - [:span {:class (stl/css :icon)} - deprecated-icon/margin-top] - [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) - :placeholder "--" - :data-name "m1" - :on-focus on-focus - :on-change on-change' - :on-blur on-blur - :nillable true - :value m1}]] - [:div {:class (stl/css :right-margin) - :title "Right margin"} - [:span {:class (stl/css :icon)} - deprecated-icon/margin-right] - [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) - :placeholder "--" - :data-name "m2" - :on-focus on-focus - :on-change on-change' - :on-blur on-blur - :nillable true - :value m2}]] + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-m1-change + :on-detach on-detach-token + :on-blur on-blur + :on-focus on-focus-m1 + :icon i/margin-top + :class (stl/css :top-margin-wrapper) + :min 0 + :name :m1 + :property "Top margin" + :nillable true + :applied-tokens {:m1 applied-token-to-m1} + :values {:m1 m1}}] - [:div {:class (stl/css :bottom-margin) - :title "Bottom margin"} - [:span {:class (stl/css :icon)} - deprecated-icon/margin-bottom] - [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) - :placeholder "--" - :data-name "m3" - :on-focus on-focus - :on-change on-change' - :on-blur on-blur - :nillable true - :value m3}]] + [:div {:class (stl/css :top-margin) + :title "Top margin"} + [:span {:class (stl/css :icon)} + deprecated-icon/margin-top] + [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) + :placeholder "--" + :data-name "m1" + :on-focus on-focus-m1 + :on-change on-m1-change + :on-blur on-blur + :nillable true + :value m1}]]) + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-m2-change + :on-detach on-detach-token + :on-blur on-blur + :on-focus on-focus-m2 + :icon i/margin-right + :class (stl/css :right-margin-wrapper) + :min 0 + :name :m2 + :align :right + :property "Right margin" + :nillable true + :applied-tokens {:m2 applied-token-to-m2} + :values {:m2 m2}}] - [:div {:class (stl/css :left-margin) - :title "Left margin"} - [:span {:class (stl/css :icon)} - deprecated-icon/margin-left] - [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) - :placeholder "--" - :data-name "m4" - :on-focus on-focus - :on-change on-change' - :on-blur on-blur - :nillable true - :value m4}]]])) + [:div {:class (stl/css :right-margin) + :title "Right margin"} + [:span {:class (stl/css :icon)} + deprecated-icon/margin-right] + [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) + :placeholder "--" + :data-name "m2" + :on-focus on-focus-m2 + :on-change on-m2-change + :on-blur on-blur + :nillable true + :value m2}]]) + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-m3-change + :on-detach on-detach-token + :on-blur on-blur + :on-focus on-focus-m3 + :icon i/margin-bottom + :class (stl/css :bottom-margin-wrapper) + :min 0 + :name :m3 + :align :right + :property "Bottom margin" + :nillable true + :applied-tokens {:m3 applied-token-to-m3} + :values {:m3 m3}}] + + [:div {:class (stl/css :bottom-margin) + :title "Bottom margin"} + [:span {:class (stl/css :icon)} + deprecated-icon/margin-bottom] + [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) + :placeholder "--" + :data-name "m3" + :on-focus on-focus-m3 + :on-change on-m3-change + :on-blur on-blur + :nillable true + :value m3}]]) + + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-m4-change + :on-detach on-detach-token + :on-blur on-blur + :on-focus on-focus-m4 + :icon i/margin-left + :class (stl/css :left-margin-wrapper) + :min 0 + :name :m4 + :property "Left margin" + :nillable true + :applied-tokens {:m4 applied-token-to-m4} + :values {:m4 m4}}] + + [:div {:class (stl/css :left-margin) + :title "Left margin"} + [:span {:class (stl/css :icon)} + deprecated-icon/margin-left] + [:> deprecated-input/numeric-input* {:class (stl/css :numeric-input) + :placeholder "--" + :data-name "m4" + :on-focus on-focus-m4 + :on-change on-m4-change + :on-blur on-blur + :nillable true + :value m4}]])])) (mf/defc margin-section* {::mf/private true - ::mf/expect-props #{:value :type :on-type-change :on-change}} + ::mf/expect-props #{:value :type :on-type-change :on-change :applied-tokens :ids}} [{:keys [type on-type-change] :as props}] (let [type (d/nilv type :simple) - on-blur (mf/use-fn #(select-margins false false false false)) + on-blur (mf/use-fn + (mf/deps select-margins) + #(select-margins false false false false)) props (mf/spread-props props {:on-blur on-blur}) on-type-change' @@ -292,8 +538,215 @@ :label "Align self end" :value "end"}]}]) +(def ^:private schema:layout-item-props-schema + [:map + [:layout-item-margin + {:optional true} + [:map + [:m1 {:optional true} [:or :float :int]] + [:m2 {:optional true} [:or :float :int]] + [:m3 {:optional true} [:or :float :int]] + [:m4 {:optional true} [:or :float :int]]]] + + [:layout-item-margin-type {:optional true} :keyword] + + [:layout-item-h-sizing {:optional true} :keyword] + [:layout-item-v-sizing {:optional true} :keyword] + + [:layout-item-min-w {:optional true} [:or :float :int]] + [:layout-item-max-w {:optional true} [:or :float :int]] + [:layout-item-min-h {:optional true} [:or :float :int]] + [:layout-item-max-h {:optional true} [:or :float :int]]]) + +(def ^:private schema:layout-size-constraints + [:map + [:values schema:layout-item-props-schema] + [:applied-tokens [:map-of :keyword :string]] + [:ids [::sm/vec ::sm/uuid]] + [:v-sizing {:optional true} [:maybe [:= :fill]]]]) + +(mf/defc layout-size-constraints* + {::mf/private true + ::mf/schema (sm/schema schema:layout-size-constraints)} + [{:keys [values v-sizing ids applied-tokens] :as props}] + (let [token-numeric-inputs + (features/use-feature "tokens/numeric-input") + + min-w (get values :layout-item-min-w) + + max-w (get values :layout-item-max-w) + + min-h (get values :layout-item-min-h) + + max-h (get values :layout-item-max-h) + + applied-token-to-min-w (get applied-tokens :layout-item-min-w) + + applied-token-to-max-w (get applied-tokens :layout-item-max-w) + + applied-token-to-min-h (get applied-tokens :layout-item-min-h) + + applied-token-to-max-h (get applied-tokens :layout-item-max-h) + + on-detach-token + (mf/use-fn + (mf/deps ids) + (fn [token attr] + (st/emit! (dwta/unapply-token {:token (first token) + :attributes #{attr} + :shape-ids ids})))) + + on-size-change + (mf/use-fn + (mf/deps ids) + (fn [value attr] + (if (or (string? value) (int? value)) + (st/emit! (dwsl/update-layout-child ids {attr value})) + (do + (st/emit! + (dwta/toggle-token {:token (first value) + :attrs #{attr} + :shape-ids ids})))))) + + on-layout-item-min-w-change + (mf/use-fn (mf/deps on-size-change) #(on-size-change % :layout-item-min-w)) + + on-layout-item-max-w-change + (mf/use-fn (mf/deps on-size-change) #(on-size-change % :layout-item-max-w)) + + on-layout-item-min-h-change + (mf/use-fn (mf/deps on-size-change) #(on-size-change % :layout-item-min-h)) + + on-layout-item-max-h-change + (mf/use-fn (mf/deps on-size-change) #(on-size-change % :layout-item-max-h))] + + [:div {:class (stl/css :advanced-options)} + (when (= (:layout-item-h-sizing values) :fill) + [:div {:class (stl/css :horizontal-fill)} + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-layout-item-min-w-change + :on-detach on-detach-token + :class (stl/css :min-w-wrapper) + :min 0 + :name :layout-item-min-w + :property (tr "workspace.options.layout-item.layout-item-min-w") + :text-icon "MIN W" + :nillable true + :applied-tokens {:layout-item-min-w applied-token-to-min-w} + :tooltip-class (stl/css :tooltip-wrapper) + :values {:layout-item-min-w min-w}}] + + [:div {:class (stl/css :layout-item-min-w) + :title (tr "workspace.options.layout-item.layout-item-min-w")} + + [:span {:class (stl/css :icon-text)} "MIN W"] + [:> deprecated-input/numeric-input* + {:class (stl/css :numeric-input) + :no-validate true + :min 0 + :data-wrap true + :placeholder "--" + :data-attr "layout-item-min-w" + :on-focus dom/select-target + :on-change on-layout-item-min-w-change + :value (get values :layout-item-min-w) + :nillable true}]]) + + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-layout-item-max-w-change + :on-detach on-detach-token + :text-icon "MAX W" + :class (stl/css :max-w-wrapper) + :min 0 + :name :layout-item-max-w + :align :right + :property (tr "workspace.options.layout-item.layout-item-max-w") + :nillable true + :tooltip-class (stl/css :tooltip-wrapper) + :applied-tokens {:layout-item-max-w applied-token-to-max-w} + :values {:layout-item-max-w max-w}}] + + [:div {:class (stl/css :layout-item-max-w) + :title (tr "workspace.options.layout-item.layout-item-max-w")} + [:span {:class (stl/css :icon-text)} "MAX W"] + [:> deprecated-input/numeric-input* + {:class (stl/css :numeric-input) + :no-validate true + :min 0 + :data-wrap true + :placeholder "--" + :data-attr "layout-item-max-w" + :on-focus dom/select-target + :on-change on-layout-item-max-w-change + :value (get values :layout-item-max-w) + :nillable true}]])]) + + (when (= v-sizing :fill) + [:div {:class (stl/css :vertical-fill)} + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-layout-item-min-h-change + :on-detach on-detach-token + :text-icon "MIN H" + :class (stl/css :min-h-wrapper) + :min 0 + :name :layout-item-min-h + :property (tr "workspace.options.layout-item.layout-item-min-h") + :nillable true + :tooltip-class (stl/css :tooltip-wrapper) + :applied-tokens {:layout-item-min-h applied-token-to-min-h} + :values {:layout-item-min-h min-h}}] + + [:div {:class (stl/css :layout-item-min-h) + :title (tr "workspace.options.layout-item.layout-item-min-h")} + [:span {:class (stl/css :icon-text)} "MIN H"] + [:> deprecated-input/numeric-input* + {:class (stl/css :numeric-input) + :no-validate true + :min 0 + :data-wrap true + :placeholder "--" + :data-attr "layout-item-min-h" + :on-focus dom/select-target + :on-change on-layout-item-min-h-change + :value (get values :layout-item-min-h) + :nillable true}]]) + + (if token-numeric-inputs + [:> numeric-input-wrapper* + {:on-change on-layout-item-max-h-change + :on-detach on-detach-token + :class (stl/css :max-h-wrapper) + :min 0 + :text-icon "MAX H" + :name :layout-item-max-h + :align :right + :property (tr "workspace.options.layout-item.layout-item-max-h") + :nillable true + :tooltip-class (stl/css :tooltip-wrapper) + :applied-tokens {:layout-item-max-h applied-token-to-max-h} + :values {:layout-item-max-h max-h}}] + + [:div {:class (stl/css :layout-item-max-h) + :title (tr "workspace.options.layout-item.layout-item-max-h")} + + [:span {:class (stl/css :icon-text)} "MAX H"] + [:> deprecated-input/numeric-input* + {:class (stl/css :numeric-input) + :no-validate true + :min 0 + :data-wrap true + :placeholder "--" + :data-attr "layout-item-max-h" + :on-focus dom/select-target + :on-change on-layout-item-max-h-change + :value (get values :layout-item-max-h) + :nillable true}]])])])) + (mf/defc layout-item-menu - {::mf/memo #{:ids :values :type :is-layout-child? :is-grid-parent :is-flex-parent? :is-grid-layout? :is-flex-layout?} + {::mf/memo #{:ids :values :type :is-layout-child? :is-grid-parent :is-flex-parent? :is-grid-layout? :is-flex-layout? :applied-tokens} ::mf/props :obj} [{:keys [ids values ^boolean is-layout-child? @@ -301,7 +754,8 @@ ^boolean is-grid-parent? ^boolean is-flex-parent? ^boolean is-flex-layout? - ^boolean is-grid-layout?]}] + ^boolean is-grid-layout? + applied-tokens]}] (let [selection-parents* (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) selection-parents (mf/deref selection-parents*) @@ -397,16 +851,7 @@ (fn [value] (st/emit! (dwsl/update-layout-child ids {:layout-item-v-sizing (keyword value)})))) - ;; Size and position - on-size-change - (mf/use-fn - (mf/deps ids) - (fn [value event] - (let [attr (-> (dom/get-current-target event) - (dom/get-data "attr") - (keyword))] - (st/emit! (dwsl/update-layout-child ids {attr value}))))) - + ;; Position on-change-position (mf/use-fn (mf/deps ids) @@ -423,7 +868,8 @@ (fn [value] (st/emit! (dwsl/update-layout-child ids {:layout-item-z-index value}))))] - [:div {:class (stl/css :element-set)} + [:section {:class (stl/css :element-set) + :aria-label "layout item menu"} [:div {:class (stl/css :element-title)} [:> title-bar* {:collapsable has-content? :collapsed (not open?) @@ -483,74 +929,13 @@ [:> margin-section* {:value (:layout-item-margin values) :type (:layout-item-margin-type values) :on-type-change on-margin-type-change + :applied-tokens applied-tokens + :ids ids :on-change on-margin-change}]) (when (or (= h-sizing :fill) (= v-sizing :fill)) - [:div {:class (stl/css :advanced-options)} - (when (= (:layout-item-h-sizing values) :fill) - [:div {:class (stl/css :horizontal-fill)} - [:div {:class (stl/css :layout-item-min-w) - :title (tr "workspace.options.layout-item.layout-item-min-w")} - - [:span {:class (stl/css :icon-text)} "MIN W"] - [:> deprecated-input/numeric-input* - {:class (stl/css :numeric-input) - :no-validate true - :min 0 - :data-wrap true - :placeholder "--" - :data-attr "layout-item-min-w" - :on-focus dom/select-target - :on-change on-size-change - :value (get values :layout-item-min-w) - :nillable true}]] - - [:div {:class (stl/css :layout-item-max-w) - :title (tr "workspace.options.layout-item.layout-item-max-w")} - [:span {:class (stl/css :icon-text)} "MAX W"] - [:> deprecated-input/numeric-input* - {:class (stl/css :numeric-input) - :no-validate true - :min 0 - :data-wrap true - :placeholder "--" - :data-attr "layout-item-max-w" - :on-focus dom/select-target - :on-change on-size-change - :value (get values :layout-item-max-w) - :nillable true}]]]) - - (when (= v-sizing :fill) - [:div {:class (stl/css :vertical-fill)} - [:div {:class (stl/css :layout-item-min-h) - :title (tr "workspace.options.layout-item.layout-item-min-h")} - - [:span {:class (stl/css :icon-text)} "MIN H"] - [:> deprecated-input/numeric-input* - {:class (stl/css :numeric-input) - :no-validate true - :min 0 - :data-wrap true - :placeholder "--" - :data-attr "layout-item-min-h" - :on-focus dom/select-target - :on-change on-size-change - :value (get values :layout-item-min-h) - :nillable true}]] - - [:div {:class (stl/css :layout-item-max-h) - :title (tr "workspace.options.layout-item.layout-item-max-h")} - - [:span {:class (stl/css :icon-text)} "MAX H"] - [:> deprecated-input/numeric-input* - {:class (stl/css :numeric-input) - :no-validate true - :min 0 - :data-wrap true - :placeholder "--" - :data-attr "layout-item-max-h" - :on-focus dom/select-target - :on-change on-size-change - :value (get values :layout-item-max-h) - :nillable true}]]])])])])) + [:> layout-size-constraints* {:ids ids + :values values + :applied-tokens applied-tokens + :v-sizing v-sizing}])])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.scss index 664fb759b8..c90a220353 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.scss @@ -6,17 +6,21 @@ @use "refactor/common-refactor.scss" as deprecated; @use "../../../sidebar/common/sidebar.scss" as sidebar; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/typography.scss" as *; +@use "ds/_utils.scss" as *; .element-set { margin: 0; } .title-spacing-layout-element { - margin: 0 0 deprecated.$s-4 0; + margin: 0 0 var(--sp-xs) 0; } .title-spacing-empty { - padding-left: deprecated.$s-2; + padding-inline-start: var(--sp-xxs); } .flex-element-menu { @@ -35,8 +39,8 @@ } .z-index-wrapper { + @include use-typography("body-small"); @extend .input-element; - @include deprecated.bodySmallTypography; grid-column: 6 / span 3; } @@ -55,7 +59,7 @@ } .position-options { - width: 100%; + inline-size: 100%; grid-column: 1 / span 5; } @@ -75,7 +79,7 @@ .vertical-margin, .horizontal-margin { @extend .input-element; - @include deprecated.bodySmallTypography; + @include use-typography("body-small"); } .vertical-margin { grid-column: 1; @@ -85,6 +89,16 @@ } } +.vertical-margin-wrapper { + grid-column: 1; + --dropdown-width: var(--7-columns-dropdown-width); +} + +.horizontal-margin-wrapper { + grid-column: 2; + --dropdown-width: var(--7-columns-dropdown-width); +} + .margin-multiple { display: grid; grid-template-columns: subgrid; @@ -96,29 +110,44 @@ .left-margin, .right-margin { @extend .input-element; - @include deprecated.bodySmallTypography; + @include use-typography("body-small"); } -.top-margin { +.top-margin, +.top-margin-wrapper { + --dropdown-width: var(--7-columns-dropdown-width); grid-column: 1; grid-row: 1; } -.bottom-margin { +.bottom-margin, +.bottom-margin-wrapper { + --dropdown-width: var(--7-columns-dropdown-width); grid-column: 2; grid-row: 1; } -.left-margin { +.left-margin, +.left-margin-wrapper { + --dropdown-width: var(--7-columns-dropdown-width); grid-column: 1; grid-row: 2; } -.right-margin { +.right-margin, +.right-margin-wrapper { + --dropdown-width: var(--7-columns-dropdown-width); grid-column: 2; grid-row: 2; } +.min-w-wrapper, +.max-w-wrapper, +.min-h-wrapper, +.max-h-wrapper { + --dropdown-width: var(--7-columns-dropdown-width); +} + .advanced-options { display: grid; grid-template-columns: @@ -140,14 +169,18 @@ .layout-item-max-w, .layout-item-max-h { @extend .input-element; - @include deprecated.bodySmallTypography; + @include use-typography("body-small"); .icon-text { justify-content: flex-start; - width: deprecated.$s-80; - padding-top: deprecated.$s-2; + inline-size: px2rem(80); + padding-block-start: var(--sp-xxs); } } .inputs-wrapper { grid-column: 1 / span 2; } + +.tooltip-wrapper { + inline-size: 100%; +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index ce4e318153..8404baada7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -114,6 +114,7 @@ :is-layout-child? true :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens applied-tokens :shape shape}]) (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index ca4b85ba0e..92c630b854 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -113,6 +113,7 @@ :is-layout-container? false :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens applied-tokens :shape shape}]) (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index a40f017873..731eba6478 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -135,6 +135,7 @@ :is-flex-layout? is-flex-layout? :is-grid-layout? is-grid-layout? :is-layout-child? is-layout-child? + :applied-tokens applied-tokens :is-layout-container? is-layout-container? :shape shape}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index 716a836a30..8b7cc718a4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -139,6 +139,7 @@ :is-layout-container? false :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens applied-tokens :values layout-item-values}]) (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 60148a4776..d2a8b25720 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -409,7 +409,7 @@ [layout-container-ids layout-container-values layout-container-tokens] (get-attrs shapes objects :layout-container) - [layout-item-ids layout-item-values {}] + [layout-item-ids layout-item-values layout-item-tokens] (get-attrs shapes objects :layout-item) components @@ -471,6 +471,7 @@ :is-layout-container? all-flex-layout-container? :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens layout-item-tokens :values layout-item-values}]) (when-not (or (empty? constraint-ids) ^boolean is-layout-child?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 95d7f4a83f..98cd626d17 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -113,6 +113,7 @@ :is-layout-container? false :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens applied-tokens :shape shape}]) (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index a6c47380e7..4113de291c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -112,6 +112,7 @@ :values layout-item-values :is-layout-child? true :is-flex-parent? is-flex-parent? + :applied-tokens applied-tokens :is-grid-parent? is-grid-parent? :shape shape}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index ca9037ea3d..c71448aaa3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -180,6 +180,7 @@ :is-layout-child? true :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens applied-tokens :shape shape}]) (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index a89a6e673e..8b3f66dd51 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -154,6 +154,7 @@ :is-layout-child? true :is-flex-parent? is-flex-parent? :is-grid-parent? is-grid-parent? + :applied-tokens applied-tokens :shape shape}]) (when (or (not ^boolean is-layout-child?) ^boolean is-layout-child-absolute?) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 44d12a0638..9727707e6c 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -6719,19 +6719,19 @@ msgstr "Advanced options" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:543 msgid "workspace.options.layout-item.layout-item-max-h" -msgstr "Max.Height" +msgstr "Max height" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:510 msgid "workspace.options.layout-item.layout-item-max-w" -msgstr "Max.Width" +msgstr "Max width" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:527 msgid "workspace.options.layout-item.layout-item-min-h" -msgstr "Min.Height" +msgstr "Min height" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs:494 msgid "workspace.options.layout-item.layout-item-min-w" -msgstr "Min.Width" +msgstr "Min width" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs #, unused diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 96c7d935a8..a06b5ee301 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -7170,11 +7170,11 @@ msgstr "Ancho" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:535, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:552 msgid "workspace.options.x" -msgstr "eje X" +msgstr "Eje X" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:545, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:563 msgid "workspace.options.y" -msgstr "eje Y" +msgstr "Eje Y" #: src/app/main/ui/workspace/viewport/path_actions.cljs:140 msgid "workspace.path.actions.add-node"