diff --git a/frontend/playwright/ui/specs/colorpicker.spec.js b/frontend/playwright/ui/specs/colorpicker.spec.js index 344aa6c18a..5b6d8253a5 100644 --- a/frontend/playwright/ui/specs/colorpicker.spec.js +++ b/frontend/playwright/ui/specs/colorpicker.spec.js @@ -85,9 +85,7 @@ test("Create a LINEAR gradient", async ({ page }) => { .last(); await inputOpacity2.fill("40"); - await expect( - workspacePage.page.getByText("Linear gradient") - ).toBeVisible(); + await expect(workspacePage.page.getByText("Linear gradient")).toBeVisible(); }); test("Create a RADIAL gradient", async ({ page }) => { @@ -161,9 +159,7 @@ test("Create a RADIAL gradient", async ({ page }) => { .last(); await inputOpacity2.fill("100"); - await expect( - workspacePage.page.getByText("Radial gradient") - ).toBeVisible(); + await expect(workspacePage.page.getByText("Radial gradient")).toBeVisible(); }); test("Gradient stops limit", async ({ page }) => { @@ -244,3 +240,82 @@ test("Bug 10089 - Cannot change alpha", async ({ page }) => { await expect(alpha).toHaveValue("50"); }); + +test("Color picker color list", async ({ page }) => { + const workspacePage = new WasmWorkspacePage(page); + await workspacePage.setupEmptyFile(); + await workspacePage.mockRPC( + /get\-file\?/, + "workspace/get-file-not-empty.json", + ); + await workspacePage.mockRPC( + "update-file?id=*", + "workspace/update-file-create-rect.json", + ); + + await workspacePage.goToWorkspace({ + fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374", + pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375", + }); + + await page.getByRole("tab", { name: "Assets" }).click(); + await page.getByRole("button", { name: "Add color" }).click(); + + const rampSelector = page.getByTestId("value-saturation-selector"); + await expect(rampSelector).toBeVisible(); + await rampSelector.click({ position: { x: 50, y: 50 } }); + await page.getByRole("button", { name: "Save color style" }).click(); + await page + .getByTestId("left-sidebar") + .locator('input[type="text"]') + .fill("first color"); + await workspacePage.page.keyboard.press("Enter"); + + await page.getByRole("button", { name: "Add color" }).click(); + await rampSelector.click({ position: { x: 40, y: 40 } }); + await page.getByRole("button", { name: "Save color style" }).click(); + await page + .getByTestId("left-sidebar") + .locator('input[type="text"]') + .fill("second color"); + await workspacePage.page.keyboard.press("Enter"); + + await page.getByRole("button", { name: "Add color" }).click(); + await rampSelector.click({ position: { x: 60, y: 60 } }); + await page.getByRole("button", { name: "Save color style" }).click(); + await page + .getByTestId("left-sidebar") + .locator('input[type="text"]') + .fill("third color"); + await workspacePage.page.keyboard.press("Enter"); + + await page.getByRole("tab", { name: "Layers" }).click(); + await workspacePage.clickLeafLayer("Rectangle"); + + const swatch = workspacePage.page.getByRole("button", { name: "#B1B2B5" }); + await swatch.click(); + + const colorpicker = workspacePage.page.getByTestId("colorpicker"); + await expect(colorpicker).toBeVisible(); + + const colorItems = colorpicker.getByRole("listitem"); + + const colorButtons = colorItems.getByRole("button"); + await expect(colorButtons).toHaveCount(3); + + const toggleButton = colorpicker.getByRole("button", { name: "List view" }); + await toggleButton.click(); + + await expect( + colorpicker.getByRole("listitem", { name: "#708191" }), + ).toBeVisible(); + await colorpicker + .getByRole("combobox") + .filter({ hasText: "Recent colors" }) + .click(); + await colorpicker.getByRole("option", { name: "File library" }).click(); + + await expect( + colorpicker.getByRole("listitem", { name: "First color" }), + ).toBeVisible(); +}); diff --git a/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss b/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss index 55b125443f..ee38eb1124 100644 --- a/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss +++ b/frontend/src/app/main/ui/ds/controls/shared/options_dropdown.scss @@ -16,7 +16,7 @@ --options-dropdown-border-color: var(--color-background-quaternary); position: absolute; - inset-block-start: $sz-36; + inset-block: var(--dropdown-start, $sz-36) var(--dropdown-end, auto); inline-size: var(--dropdown-width, 100%); transform: translateX(var(--dropdown-translate-distance, 0)); background-color: var(--options-dropdown-bg-color); diff --git a/frontend/src/app/main/ui/ds/utilities/swatch.cljs b/frontend/src/app/main/ui/ds/utilities/swatch.cljs index 87e9da34c8..9e552e8744 100644 --- a/frontend/src/app/main/ui/ds/utilities/swatch.cljs +++ b/frontend/src/app/main/ui/ds/utilities/swatch.cljs @@ -11,6 +11,7 @@ (:require [app.common.data.macros :as dm] [app.common.json :as json] + [app.common.math :as mth] [app.common.schema :as sm] [app.common.types.color :as ct] [app.config :as cfg] @@ -20,7 +21,7 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(defn- color-title +(defn color-title [color-item] (let [{:keys [name path]} (meta color-item) @@ -31,10 +32,16 @@ gradient (:gradient color-item) image (:image color-item) - color (:color color-item)] + color (:color color-item) + opacity (:opacity color-item) + opacity-text (when (< (:opacity color-item) 1) + (str (mth/round (* (:opacity color-item) 100)) "%"))] (if (some? name) (cond + (and (some? opacity) (some? color) (< (:opacity color-item) 1)) + (str/ffmt "% (% - %)" path-and-name color opacity-text) + (some? color) (str/ffmt "% (%)" path-and-name color) @@ -48,6 +55,9 @@ path-and-name) (cond + (and (some? opacity) (some? color) (< (:opacity color-item) 1)) + (str/ffmt "% (%)" color opacity-text) + (some? color) color diff --git a/frontend/src/app/main/ui/workspace/colorpicker.scss b/frontend/src/app/main/ui/workspace/colorpicker.scss index 8a585016ef..b9fe67d6f5 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.scss +++ b/frontend/src/app/main/ui/workspace/colorpicker.scss @@ -14,9 +14,11 @@ .colorpicker-tooltip { @extend %modal-background; + --colorpicker-width: #{$sz-284}; + left: calc(10 * px2rem(140)); padding: var(--sp-m); - width: $sz-284; + width: var(--colorpicker-width); overflow: auto; display: flex; flex-direction: column; diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index a101adb50a..d69d06a7d5 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -9,6 +9,8 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.math :as mth] + [app.common.path-names :as cpn] [app.common.types.color :as ctc] [app.common.uuid :as uuid] [app.main.data.event :as ev] @@ -16,26 +18,196 @@ [app.main.data.workspace.colors :as mdc] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.color-bullet :as cb] - [app.main.ui.components.select :refer [select]] [app.main.ui.context :as ctx] + [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.select :refer [select*]] + [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.ds.product.empty-state :refer [empty-state*]] + [app.main.ui.ds.tooltip :refer [tooltip*]] + [app.main.ui.ds.utilities.swatch :refer [swatch*] :as su] [app.main.ui.hooks :as h] [app.main.ui.hooks.resize :as r] - [app.main.ui.icons :as deprecated-icon] + [app.main.ui.workspace.sidebar.assets.groups :as grp] + [app.util.color :as uc] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) +;; --------------------------------------------------------------------------- +;; Private helpers +;; --------------------------------------------------------------------------- + +(defn- convert-grouped-colors + "Walk the nested group tree produced by `grp/group-assets` and replace + every raw library color (at the leaf `\"\"` vectors) with its converted + form via `ctc/library-color->color`. Done once inside the effect so the + render path never needs to call `library-color->color`." + [groups resolved-file-id] + (reduce-kv + (fn [acc group-key value] + (assoc acc group-key + (if (= group-key "") + ;; leaf vector — convert each raw color + (mapv #(ctc/library-color->color % resolved-file-id) value) + ;; nested sub-tree — recurse, preserving sorted-map type + (convert-grouped-colors value resolved-file-id)))) + (empty groups) + groups)) + +;; --------------------------------------------------------------------------- +;; Color row +;; --------------------------------------------------------------------------- + +(mf/defc color-row-colorpicker* + "Single color row for the list view. Memoized so it only re-renders when + `color-item` or `on-click` actually changes." + {::mf/memo true + ::mf/private true} + [{:keys [color-item on-click]}] + (let [gradient (:gradient color-item) + image (:image color-item) + color (:color color-item) + {:keys [name]} (meta color-item) + element-id (mf/use-id) + element-ref (mf/use-ref nil) + + handle-click + (mf/use-fn + (mf/deps on-click color-item) + (fn [_] (on-click color-item))) + opacity-text (str " (" (mth/round (* (:opacity color-item) 100)) "%)") + gradient-text (str " (" (uc/gradient-type->string (:type gradient)) ")")] + + [:> tooltip* {:content (su/color-title color-item) + :trigger-ref element-ref + :id element-id} + + [:li {:aria-labelledby element-id + :ref element-ref} + [:button {:class (stl/css :color-row-colorpicker) + :on-key-down (fn [e] + (when (or (= (.-key e) "Enter") (= (.-key e) " ")) + (.preventDefault e) + (handle-click e))) + :on-click handle-click} + + [:> swatch* {:background color-item + :show-tooltip false + :size "medium"}] + (cond + gradient + (if name + [:span {:class (stl/css :color-row-colorpicker-label)} + (str name) + [:span {:class (stl/css :color-row-colorpicker-gradient-type)} + gradient-text]] + + [:span {:class (stl/css :color-row-colorpicker-label)} + (tr "media.gradient") + [:span {:class (stl/css :color-row-colorpicker-gradient-type)} + gradient-text]]) + + image + [:span (tr "media.image")] + + color + (if name + [:span {:class (stl/css :color-row-colorpicker-label)} + name + (when (and (number? (:opacity color-item)) (< (:opacity color-item) 1)) + [:span {:class (stl/css :color-row-colorpicker-opacity)} + opacity-text])] + [:span {:class (stl/css :color-row-colorpicker-label)} + color + (when (and (number? (:opacity color-item)) (< (:opacity color-item) 1)) + [:span {:class (stl/css :color-row-colorpicker-opacity)} + opacity-text])]) + + :else + [:span (tr "labels.other")])]]])) + +;; --------------------------------------------------------------------------- +;; Grouped color list +;; --------------------------------------------------------------------------- + +(mf/defc color-group-list* + "Renders library colors organised by group/path in list view. + `groups` nested map produced by `grp/group-assets`, with colors + already converted via `convert-grouped-colors` + `prefix` accumulated group label (empty string at root) + `resolved-file-id` UUID of the library the colors belong to + `on-color-click` called with the converted color map on click + `open-groups` set of group paths that are currently collapsed + `on-toggle-group` (fn [path]) to toggle a group open/closed" + {::mf/memo true + ::mf/private true} + [{:keys [groups prefix resolved-file-id on-color-click open-groups on-toggle-group]}] + (let [direct-colors (get groups "") + subgroups (dissoc groups "") + is-root? (empty? prefix) + collapsed? (and (not is-root?) (contains? open-groups prefix)) + handle-toggle-group (mf/use-fn + (mf/deps prefix on-toggle-group) + (fn [_] + (on-toggle-group prefix)))] + [:* + (when (not is-root?) + [:div {:class (stl/css :color-group-header) + :role "button" + :tab-index 0 + :aria-expanded (not collapsed?) + :aria-label prefix + :on-key-down (fn [e] + (when (or (= (.-key e) "Enter") (= (.-key e) " ")) + (.preventDefault e) + (handle-toggle-group e))) + :on-click handle-toggle-group} + [:> i/icon* {:icon-id (if collapsed? i/arrow-right i/arrow-down) + :size "s" + :class (stl/css :color-group-arrow)}] + [:span {:class (stl/css :color-group-name)} prefix]]) + + (when-not collapsed? + [:* + (for [color direct-colors] + [:> color-row-colorpicker* + {:key (dm/str (:ref-id color)) + :color-item color + :on-click on-color-click}]) + + (for [[group-name sub-tree] subgroups] + [:> color-group-list* + {:key group-name + :groups sub-tree + :prefix (cpn/merge-path-item-with-dot prefix group-name) + :resolved-file-id resolved-file-id + :on-color-click on-color-click + :open-groups open-groups + :on-toggle-group on-toggle-group}])])])) + +;; --------------------------------------------------------------------------- +;; Libraries panel +;; --------------------------------------------------------------------------- + (mf/defc libraries* [{:keys [state on-select-color on-add-library-color disable-gradient disable-opacity disable-image]}] (let [selected* (h/use-shared-state mdc/colorpicker-selected-broadcast-key :recent) selected (deref selected*) + view-mode* (mf/use-state :grid) + view-mode (deref view-mode*) + file-id (mf/use-ctx ctx/current-file-id) current-colors* (mf/use-state []) current-colors (deref current-colors*) + grouped-colors* (mf/use-state {}) + grouped-colors (deref grouped-colors*) + + open-groups* (mf/use-state #{}) + open-groups (deref open-groups*) + libraries (mf/deref refs/libraries) recent-colors (mf/deref refs/recent-colors) recent-colors (mf/with-memo [recent-colors] @@ -43,15 +215,15 @@ library-options (mf/with-memo [] - [{:value "recent" :label (tr "workspace.libraries.colors.recent-colors")} - {:value "file" :label (tr "workspace.libraries.colors.file-library")}]) + [{:value "recent" :label (tr "workspace.libraries.colors.recent-colors") :id "recent"} + {:value "file" :label (tr "workspace.libraries.colors.file-library") :id "file"}]) options (mf/with-memo [library-options libraries file-id] (into library-options (comp (map val) - (map (fn [lib] {:value (d/name (:id lib)) :label (:name lib)}))) + (map (fn [lib] {:value (d/name (:id lib)) :label (:name lib) :id (d/name (:id lib))}))) (dissoc libraries file-id))) on-library-change @@ -81,63 +253,149 @@ (-> (mdc/show-palette selected) (vary-meta assoc ::ev/origin "workspace-colorpicker"))))) + toggle-view-mode + (mf/use-fn + (mf/deps view-mode) + (fn [] + (let [new-mode (if (= view-mode :grid) :list :grid)] + (reset! view-mode* new-mode)))) + on-color-click (mf/use-fn (mf/deps state selected on-select-color) - (fn [event] + (fn [color] (when-not (= :recent selected) (st/emit! (ev/event {::ev/name "use-library-color" ::ev/origin "colorpicker" :external-library (not= :file selected)}))) - (on-select-color state event)))] + (on-select-color state color))) - ;; Load library colors when the select is changed + on-toggle-group + (mf/use-fn + (fn [path] + (swap! open-groups* + (fn [s] + (if (contains? s path) + (disj s path) + (conj s path))))))] + + ;; Load library colors when the selected library (or filter options) change. + ;; + ;; flat current-colors* -- used for the grid view and the recent list view. + ;; grouped grouped-colors* -- used for the library grouped list view. + ;; + ;; Library colors are fully converted with `library-color->color` here so + ;; the render path never needs to do it. `flat-colors` is materialised as + ;; an eager vector so realisation does not leak into render time. + ;; open-groups* is reset to #{} (all groups expanded) on every library switch. (mf/with-effect [selected recent-colors libraries file-id valid-color?] - (let [file-id (if (= selected :file) - file-id - selected) + (let [resolved-file-id (if (= selected :file) file-id selected)] + (reset! open-groups* #{}) + (if (= selected :recent) + (let [colors (into [] + (comp + (filter valid-color?) + (map-indexed (fn [index color] + (let [color (if (map? color) color {:color color})] + (vary-meta color assoc ::id (dm/str index))))) + (take-while some?)) + (sort ctc/sort-colors (reverse recent-colors)))] + (reset! current-colors* colors) + (reset! grouped-colors* {})) - colors (if (= selected :recent) - ;; NOTE: The `map?` check is to keep backwards - ;; compatibility We transform from string to map - (->> (reverse recent-colors) - (filter valid-color?) - (map-indexed (fn [index color] - (let [color (if (map? color) color {:color color})] - (vary-meta color assoc ::id (dm/str index))))) - (sort ctc/sort-colors)) - (->> (dm/get-in libraries [file-id :data :colors]) - (vals) - (filter valid-color?) - (sort-by :name) - (map #(ctc/library-color->color % file-id)) - (map-indexed (fn [index color] - (vary-meta color assoc ::id (dm/str index))))))] + (let [raw-colors (->> (dm/get-in libraries [resolved-file-id :data :colors]) + (vals) + (filter valid-color?) + (sort-by :name)) - (reset! current-colors* colors))) + ;; Eager vector for the grid view -- index-based ::id for keying. + flat-colors (into [] + (map-indexed (fn [index color] + (-> (ctc/library-color->color color resolved-file-id) + (vary-meta assoc ::id (dm/str index))))) + raw-colors) + + ;; Group tree with colors already converted -- no conversions at render time. + grouped (some-> (grp/group-assets raw-colors false) + (convert-grouped-colors resolved-file-id))] + (reset! current-colors* flat-colors) + (reset! grouped-colors* (or grouped {})))))) [:div {:class (stl/css :libraries)} [:div {:class (stl/css :select-wrapper)} - [:& select - {:class (stl/css :shadow-type-select) - :data-direction "up" - :default-value (or (d/name selected) "recent") - :options options - :on-change on-library-change}]] - [:div {:class (stl/css :selected-colors)} + [:> select* {:on-change on-library-change + :options options + :class (stl/css :library-select) + :default-selected (or (d/name selected) "recent")}] + + [:> icon-button* + {:variant "ghost" + :aria-label (tr "workspace.libraries.colors.show-color-palette") + :on-click toggle-palette + :icon i/swatches}] + + [:> icon-button* + {:variant "ghost" + :aria-label (if (= :grid view-mode) + (tr "workspace.assets.list-view") + (tr "workspace.assets.grid-view")) + :on-click toggle-view-mode + :icon (if (= :grid view-mode) + i/view-as-list + i/view-as-icons)}] + (when (= selected :file) - [:button {:class (stl/css :add-color-btn) - :on-click on-add-library-color} - deprecated-icon/add]) + [:> icon-button* + {:variant "ghost" + :aria-label (tr "workspace.libraries.colors.add-library-color") + :on-click on-add-library-color + :icon i/add}])] - [:button {:class (stl/css :palette-btn) - :on-click toggle-palette} - deprecated-icon/swatches] + (if (= view-mode :grid) + ;; Grid view + (if (seq current-colors) + [:ul {:class (stl/css :selected-colors) + :aria-label (tr "workspace.assets.colors")} + (for [color current-colors] + [:li {:key (-> color meta ::id)} + [:> swatch* {:background color + :on-click on-color-click + :size "medium"}]])] + [:> empty-state* {:icon "swatches" + :class (stl/css :empty-state) + :text (if (= selected :recent) + (tr "workspace.libraries.colors.empty-recent-colors") + (tr "workspace.libraries.colors.empty-palette"))}]) - (for [color current-colors] - [:> cb/color-bullet* - {:key (-> color meta ::id) - :color color - :on-click on-color-click}])]])) + ;; List view + (if (= selected :recent) + ;; Recent colors -- flat list or empty state + (if (seq current-colors) + [:ul {:class (stl/css :selected-colors-list) + :aria-label (tr "workspace.assets.colors")} + (for [color current-colors] + [:> color-row-colorpicker* + {:key (-> color meta ::id) + :color-item color + :on-click on-color-click}])] + [:> empty-state* {:icon "swatches" + :class (stl/css :empty-state) + :text (tr "workspace.libraries.colors.empty-recent-colors")}]) + + ;; Library colors -- grouped list view or empty state + (if (seq grouped-colors) + (let [resolved-file-id (if (= selected :file) file-id selected)] + [:ul {:class (stl/css :selected-colors-list) + :aria-label (tr "workspace.assets.colors")} + [:> color-group-list* + {:groups grouped-colors + :prefix "" + :resolved-file-id resolved-file-id + :on-color-click on-color-click + :open-groups open-groups + :on-toggle-group on-toggle-group}]]) + [:> empty-state* {:icon "swatches" + :class (stl/css :empty-state) + :text (tr "workspace.libraries.colors.empty-palette")}])))])) diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.scss b/frontend/src/app/main/ui/workspace/colorpicker/libraries.scss index b3e445e654..63b7d44d63 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.scss +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.scss @@ -4,42 +4,116 @@ // // Copyright (c) KALEIDOS INC Sucursal en España SL -@use "refactor/common-refactor.scss" as deprecated; +@use "ds/typography.scss" as t; +@use "ds/_sizes.scss" as *; +@use "ds/colors.scss" as *; +@use "ds/mixins.scss" as *; +@use "ds/_utils.scss" as *; +@use "ds/borders.scss" as *; .libraries { - margin-top: deprecated.$s-8; - width: 100%; + margin-block-start: var(--sp-s); + inline-size: 100%; } .selected-colors { display: grid; grid-template-columns: repeat(8, 1fr); - gap: deprecated.$s-4; justify-content: space-between; + gap: var(--sp-xs); overflow: auto; - margin-top: deprecated.$s-8; - max-height: deprecated.$s-168; + margin-block-start: var(--sp-s); + max-block-size: px2rem(168); + padding-inline-start: var(--sp-xxs); } -.add-color-btn, -.palette-btn { - @extend %button-secondary; +.selected-colors-list { + display: flex; + flex-direction: column; + gap: var(--sp-xs); + overflow: auto; + margin-block-start: var(--sp-s); + max-block-size: px2rem(168); +} - height: deprecated.$s-24; - width: deprecated.$s-24; - border-radius: deprecated.$br-circle; - padding: 0; +.color-row-colorpicker-label { + @include t.use-typography("body-small"); + @include text-ellipsis; - svg { - @extend %button-icon; + inline-size: calc(var(--colorpicker-width) - #{px2rem(67)}); + color: var(--color-foreground-primary); + text-align: left; +} + +.color-row-colorpicker-gradient-type, +.color-row-colorpicker-opacity { + color: var(--color-foreground-secondary); +} + +.color-row-colorpicker { + border: none; + background: none; + cursor: pointer; + display: grid; + grid-template-columns: auto 1fr; + gap: var(--sp-xs); + min-block-size: $sz-32; + inline-size: calc(var(--colorpicker-width) - #{px2rem(35)}); + align-items: center; + justify-content: start; + border-radius: $br-8; + padding-block: 0; + padding-inline: var(--sp-xs); + + &:hover { + background: var(--color-background-quaternary); } } +.color-group-header { + display: flex; + align-items: center; + min-block-size: $sz-32; + gap: var(--sp-xs); + padding-block: var(--sp-xs); + padding-inline: var(--sp-xs); + cursor: pointer; + border-radius: $br-8; + user-select: none; +} + +.color-group-arrow { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: var(--color-foreground-secondary); +} + +.color-group-name { + @include t.use-typography("body-small"); + @include text-ellipsis; + + cursor: pointer; + color: var(--color-foreground-secondary); +} + .selected-colors::after { content: ""; flex: auto; } .select-wrapper { + --dropdown-start: auto; + --dropdown-end: #{$sz-36}; + --dropdown-width: var(--seven-columns-width); + + display: flex; + align-items: center; overflow: initial; + gap: var(--sp-xs); +} + +.empty-state { + margin-block: px2rem(40) px2rem(48); } diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 7a70622ece..64dc9c4a41 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -6975,6 +6975,18 @@ msgstr "HSV" msgid "workspace.libraries.colors.recent-colors" msgstr "Recent colors" +#: src/app/main/ui/workspace/colorpicker/libraries.cljs +msgid "workspace.libraries.colors.add-library-color" +msgstr "Add color to library" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs +msgid "workspace.libraries.colors.show-color-palette" +msgstr "Show color palette" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs +msgid "workspace.libraries.colors.empty-recent-colors" +msgstr "There are no recent colors yet" + #: src/app/main/ui/workspace/colorpicker.cljs #, unused msgid "workspace.libraries.colors.rgb-complementary" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 3a4de46821..9050f78a21 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -6807,6 +6807,18 @@ msgstr "HSV" msgid "workspace.libraries.colors.recent-colors" msgstr "Colores recientes" +#: src/app/main/ui/workspace/colorpicker/libraries.cljs +msgid "workspace.libraries.colors.add-library-color" +msgstr "Añadir color a la biblioteca del archivo" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs +msgid "workspace.libraries.colors.show-color-palette" +msgstr "Mostrar paleta de colores" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs +msgid "workspace.libraries.colors.empty-recent-colors" +msgstr "Aún no hay colores recientes" + #: src/app/main/ui/workspace/colorpicker.cljs #, unused msgid "workspace.libraries.colors.rgb-complementary"