mirror of
https://github.com/penpot/penpot.git
synced 2026-06-09 08:52:05 +00:00
🎉 Add color list to colorpicker (#9953)
* 🎉 Add color list to colorpicker * 🎉 Improve performance * 🎉 Add accessibility roles * 🎉 Add test * 🎉 Add empty state
This commit is contained in:
parent
892869b039
commit
c3f107e830
@ -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();
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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")}])))]))
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user