Add color token on colorpicker (#7197)

*  Add token aplication to colorpicker

* 🐛 Change fn name

* 🐛 Change scss from file

* 🐛 Change color for direct-color

* 🐛 Remove vector from fns

* 🐛 Fix CI

* 🐛 Change color-option name

* 🐛 Fix comments

* 🐛 Remove sets without color tokens
This commit is contained in:
Eva Marco
2025-09-11 09:13:43 +02:00
committed by GitHub
parent 99a100ad63
commit aecaf51953
28 changed files with 885 additions and 187 deletions

View File

@@ -120,6 +120,7 @@
:tiered-file-data-storage
:token-units
:token-base-font-size
:token-color
:token-typography-types
:token-typography-composite
:transit-readable-response

View File

@@ -115,7 +115,7 @@
(f shape-ids {:color hex :opacity opacity} 0 {:ignore-touched true
:page-id page-id}))))
(defn- value->color
(defn value->color
"Transform a token color value into penpot color data structure"
[color]
(when-let [tc (tinycolor/valid-color color)]
@@ -584,6 +584,35 @@
:shape-ids shape-ids
:on-update-shape on-update-shape}))))))))
(defn apply-token-on-selected
[color-operations token]
(ptk/reify ::apply-token-on-selected
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(->> (rx/from color-operations)
(rx/map
(fn [cop]
(let [shape-ids [(:shape-id cop)]]
(case (:prop cop)
:fill (apply-token {:attributes #{:fill}
:token token
:shape-ids shape-ids
:on-update-shape update-fill})
:stroke (apply-token {:attributes #{:stroke-color}
:token token
:shape-ids shape-ids
:on-update-shape update-stroke-color})
;; Text
:content (apply-token {:attributes #{:fill}
:token token
:shape-ids shape-ids
:on-update-shape update-fill})
:shadow (rx/empty))))))
(rx/of (dwu/commit-undo-transaction undo-id)))))))
;; Map token types to different properties used along the cokde ---------------------------------------------
;; FIXME: the values should be lazy evaluated, probably a function,

View File

@@ -7,6 +7,7 @@
@import "refactor/common-refactor.scss";
.title-bar {
@include headlineSmallTypography;
display: flex;
align-items: center;
justify-content: space-between;
@@ -20,7 +21,6 @@
.title,
.title-only,
.inspect-title {
@include headlineSmallTypography;
display: grid;
align-items: center;
justify-content: flex-start;

View File

@@ -7,9 +7,11 @@
@use "./utils.scss" as *;
// TODO: create actual tokens once we have them from design
$sz-1: px2rem(1);
$sz-6: px2rem(6);
$sz-16: px2rem(16);
$sz-24: px2rem(24);
$sz-28: px2rem(28);
$sz-32: px2rem(32);
$sz-36: px2rem(36);
$sz-40: px2rem(40);
@@ -26,6 +28,7 @@ $sz-318: px2rem(318);
$sz-352: px2rem(352);
$sz-384: px2rem(384);
$sz-400: px2rem(400);
$sz-430: px2rem(430);
$sz-480: px2rem(480);
$sz-500: px2rem(500);
$sz-964: px2rem(964);

View File

@@ -7,7 +7,6 @@
@use "../spacing.scss" as *;
@use "../borders.scss" as *;
@use "../sizes.scss" as *;
@use "../typography.scss" as t;
.input-wrapper {
--opacity-button: 0;

View File

@@ -172,7 +172,9 @@
[:id {:optional true} :string]
[:offset {:optional true} :int]
[:delay {:optional true} :int]
[:content [:or fn? :string [:fn mf/element?]]]
;; TODO: Review why html element crash schema
;; https://tree.taiga.io/project/penpot/task/12039
;; [:content [:or fn? :string [:fn mf/element?]]]
[:placement {:optional true}
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]])

View File

@@ -14,6 +14,7 @@
[app.common.schema :as sm]
[app.common.types.color :as ct]
[app.config :as cfg]
[app.main.ui.ds.tooltip :refer [tooltip*]]
[app.util.color :as uc]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
@@ -66,7 +67,7 @@
(mf/defc swatch*
{::mf/schema (sm/schema schema:swatch)}
[{:keys [background on-click size active class]
[{:keys [background on-click size active class tooltip-content]
:rest props}]
(let [;; NOTE: this code is only relevant for storybook, because
;; storybook is unable to pass in a comfortable way a complex
@@ -90,6 +91,7 @@
:stops gradient-stops}
image (:image background)
format (if id? "rounded" "square")
element-id (mf/use-id)
class
(dm/str class " " (stl/css-case
@@ -106,23 +108,26 @@
(mf/spread-props props {:class class
:on-click on-click
:type button-type
:title (color-title background)})]
:aria-labelledby element-id})]
[:> element-type props
(cond
[:> tooltip* {:content (if tooltip-content
tooltip-content
(color-title background))
:id element-id}
[:> element-type props
(cond
(some? gradient-type)
[:span {:class (stl/css :swatch-gradient)
:style {:background-image (str (uc/gradient->css gradient-data) ", repeating-conic-gradient(lightgray 0% 25%, white 0% 50%)")}}]
(some? gradient-type)
[:span {:class (stl/css :swatch-gradient)
:style {:background-image (str (uc/gradient->css gradient-data) ", repeating-conic-gradient(lightgray 0% 25%, white 0% 50%)")}}]
(some? image)
(let [uri (cfg/resolve-file-media image)]
[:span {:class (stl/css :swatch-image)
:style {:background-image (str/ffmt "url(%)" uri)}}])
(some? image)
(let [uri (cfg/resolve-file-media image)]
[:span {:class (stl/css :swatch-image)
:style {:background-image (str/ffmt "url(%)" uri)}}])
:else
[:span {:class (stl/css :swatch-opacity)}
[:span {:class (stl/css :swatch-solid-side)
:style {:background (uc/color->background (assoc background :opacity 1))}}]
[:span {:class (stl/css :swatch-opacity-side)
:style {:background (uc/color->background background)}}]])]))
:else
[:span {:class (stl/css :swatch-opacity)}
[:span {:class (stl/css :swatch-solid-side)
:style {:background (uc/color->background (assoc background :opacity 1))}}]
[:span {:class (stl/css :swatch-opacity-side)
:style {:background (uc/color->background background)}}]])]]))

View File

@@ -17,7 +17,7 @@
--checkerboard-background: repeating-conic-gradient(lightgray 0% 25%, white 0% 50%);
--checkerboard-size: 0.5rem 0.5rem;
border: 1px solid var(--border-color);
border: $b-1 solid var(--border-color);
border-radius: var(--border-radius);
overflow: hidden;

View File

@@ -263,6 +263,7 @@
(def ^:icon tokens (icon-xref :tokens))
(def ^:icon to-corner (icon-xref :to-corner))
(def ^:icon to-curve (icon-xref :to-curve))
(def ^:icon tokens (icon-xref :tokens))
(def ^:icon tree (icon-xref :tree))
(def ^:icon unlock (icon-xref :unlock))
(def ^:icon user (icon-xref :user))

View File

@@ -13,6 +13,7 @@
[app.common.geom.point :as gpt]
[app.common.types.color :as cc]
[app.common.types.fills :as types.fills]
[app.common.types.tokens-lib :as ctob]
[app.config :as cfg]
[app.main.data.event :as-alias ev]
[app.main.data.modal :as modal]
@@ -26,12 +27,14 @@
[app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.radio-buttons :refer [radio-buttons radio-button]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]]
[app.main.ui.workspace.colorpicker.color-tokens :refer [token-section*]]
[app.main.ui.workspace.colorpicker.gradients :refer [gradients*]]
[app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]]
[app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]]
@@ -90,12 +93,20 @@
(dom/set-css-property! node "--saturation-grad-to" (format-hsl hsl-to)))))
(mf/defc colorpicker
{::mf/props :obj}
[{:keys [data disable-gradient disable-opacity disable-image on-change on-accept]}]
[{:keys [data disable-gradient disable-opacity disable-image on-change on-accept origin combined-tokens color-origin on-token-change]}]
(let [state (mf/deref refs/colorpicker)
node-ref (mf/use-ref)
should-update? (mf/use-var true)
token-color (contains? cfg/flags :token-color)
color-style* (mf/use-state :direct-color)
color-style (deref color-style*)
toggle-token-color
(mf/use-fn
(mf/deps color-style)
(fn []
(let [new-style (if (= :direct-color color-style) :token-color :direct-color)]
(reset! color-style* new-style))))
;; TODO: I think we need to put all this picking state under
;; the same object for avoid creating adhoc refs for each
@@ -382,8 +393,10 @@
:ref node-ref
:style {:touch-action "none"}}
[:div {:class (stl/css :top-actions)}
[:div {:class (stl/css :top-actions-right)}
(when (= :gradient selected-mode)
(when (and (= color-style :direct-color)
(= :gradient selected-mode))
[:div {:class (stl/css :opacity-input-wrapper)}
[:span {:class (stl/css :icon-text)} "%"]
[:> numeric-input*
@@ -394,118 +407,143 @@
:min 0
:max 100}]])
(when (or (not disable-gradient) (not disable-image))
(when (and (= color-style :direct-color)
(or (not disable-gradient) (not disable-image)))
[:div {:class (stl/css :select)}
[:& select
{:default-value selected-mode
:options options
:on-change handle-change-mode}]])]
:on-change handle-change-mode}]])
(when (not= selected-mode :image)
(when (and (= origin :sidebar) token-color)
[:& radio-buttons {:selected color-style
:on-change toggle-token-color
:name "color-style"}
[:& radio-button {:icon deprecated-icon/swatches
:value :direct-color
:title (tr "labels.color")
:id "opt-color"}]
[:& radio-button {:icon deprecated-icon/tokens
:value :token-color
:title (tr "workspace.colorpicker.color-tokens")
:id "opt-token-color"}]])]
(when (and (not= selected-mode :image)
(= color-style :direct-color))
[:button {:class (stl/css-case :picker-btn true
:selected picking-color?)
:on-click handle-click-picker}
deprecated-icon/picker])]
deprecated-icon/picker])
(when (= selected-mode :gradient)
[:> gradients*
{:type (:type state)
:stops (if cap-stops? (vec (take types.fills/MAX-GRADIENT-STOPS (:stops state))) (:stops state))
:editing-stop (:editing-stop state)
:on-stop-edit-start handle-stop-edit-start
:on-stop-edit-finish handle-stop-edit-finish
:on-select-stop handle-change-gradient-selected-stop
:on-change-type handle-change-gradient-type
:on-change-stop handle-gradient-change-stop
:on-add-stop-auto handle-gradient-add-stop-auto
:on-add-stop-preview handle-gradient-add-stop-preview
:on-remove-stop handle-gradient-remove-stop
:on-rotate-stops handle-rotate-stops
:on-reverse-stops handle-reverse-stops
:on-reorder-stops handle-reorder-stops}])
(if (= selected-mode :image)
(let [uri (cfg/resolve-file-media (:image current-color))
keep-aspect-ratio? (-> current-color :image :keep-aspect-ratio)]
[:div {:class (stl/css :select-image)}
[:div {:class (stl/css :content)}
(when (:image current-color)
[:img {:src uri}])]
(when (some? (:image current-color))
[:div {:class (stl/css :checkbox-option)}
[:label {:for "keep-aspect-ratio"
:class (stl/css-case :global/checked keep-aspect-ratio?)}
[:span {:class (stl/css-case :global/checked keep-aspect-ratio?)}
(when keep-aspect-ratio?
deprecated-icon/status-tick)]
(tr "media.keep-aspect-ratio")
[:input {:type "checkbox"
:id "keep-aspect-ratio"
:checked keep-aspect-ratio?
:on-change handle-change-keep-aspect-ratio}]]])
[:button
{:class (stl/css :choose-image)
:title (tr "media.choose-image")
:aria-label (tr "media.choose-image")
:on-click on-fill-image-click}
(tr "media.choose-image")
[:& file-uploader
{:input-id "fill-image-upload"
:accept "image/jpeg,image/png"
:multi false
:ref fill-image-ref
:on-selected on-fill-image-selected}]]])
(when (= color-style :token-color)
[:div {:class (stl/css :token-color-title)}
(tr "workspace.colorpicker.color-tokens")])]
(if (= color-style :direct-color)
[:*
[:div {:class (stl/css :colorpicker-tabs)}
[:> tab-switcher* {:tabs tabs
:selected active-color-tab
:on-change on-change-tab}
(if picking-color?
[:div {:class (stl/css :picker-detail-wrapper)}
[:div {:class (stl/css :center-circle)}]
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
(when (= selected-mode :gradient)
[:> gradients*
{:type (:type state)
:stops (if cap-stops? (vec (take types.fills/MAX-GRADIENT-STOPS (:stops state))) (:stops state))
:editing-stop (:editing-stop state)
:on-stop-edit-start handle-stop-edit-start
:on-stop-edit-finish handle-stop-edit-finish
:on-select-stop handle-change-gradient-selected-stop
:on-change-type handle-change-gradient-type
:on-change-stop handle-gradient-change-stop
:on-add-stop-auto handle-gradient-add-stop-auto
:on-add-stop-preview handle-gradient-add-stop-preview
:on-remove-stop handle-gradient-remove-stop
:on-rotate-stops handle-rotate-stops
:on-reverse-stops handle-reverse-stops
:on-reorder-stops handle-reorder-stops}])
(if (= selected-mode :image)
(let [uri (cfg/resolve-file-media (:image current-color))
keep-aspect-ratio? (-> current-color :image :keep-aspect-ratio)]
[:div {:class (stl/css :select-image)}
[:div {:class (stl/css :content)}
(when (:image current-color)
[:img {:src uri}])]
(when (some? (:image current-color))
[:div {:class (stl/css :checkbox-option)}
[:label {:for "keep-aspect-ratio"
:class (stl/css-case :global/checked keep-aspect-ratio?)}
[:span {:class (stl/css-case :global/checked keep-aspect-ratio?)}
(when keep-aspect-ratio?
deprecated-icon/status-tick)]
(tr "media.keep-aspect-ratio")
[:input {:type "checkbox"
:id "keep-aspect-ratio"
:checked keep-aspect-ratio?
:on-change handle-change-keep-aspect-ratio}]]])
[:button
{:class (stl/css :choose-image)
:title (tr "media.choose-image")
:aria-label (tr "media.choose-image")
:on-click on-fill-image-click}
(tr "media.choose-image")
[:& file-uploader
{:input-id "fill-image-upload"
:accept "image/jpeg,image/png"
:multi false
:ref fill-image-ref
:on-selected on-fill-image-selected}]]])
[:*
[:div {:class (stl/css :colorpicker-tabs)}
[:> tab-switcher* {:tabs tabs
:selected active-color-tab
:on-change on-change-tab}
(if picking-color?
[:div {:class (stl/css :picker-detail-wrapper)}
[:div {:class (stl/css :center-circle)}]
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
(case active-color-tab
"ramp"
[:> ramp-selector*
{:color current-color
:disable-opacity disable-opacity
:on-change handle-change-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag}]
(case active-color-tab
"ramp"
[:> ramp-selector*
{:color current-color
:disable-opacity disable-opacity
:on-change handle-change-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag}]
"harmony"
[:& harmony-selector
{:color current-color
:disable-opacity disable-opacity
:on-change handle-change-color
:on-start-drag on-start-drag}]
"harmony"
[:& harmony-selector
{:color current-color
:disable-opacity disable-opacity
:on-change handle-change-color
:on-start-drag on-start-drag}]
"hsva"
[:& hsva-selector
{:color current-color
:disable-opacity disable-opacity
:on-change handle-change-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag}]))]]
"hsva"
[:& hsva-selector
{:color current-color
:disable-opacity disable-opacity
:on-change handle-change-color
:on-start-drag on-start-drag
:on-finish-drag on-finish-drag}]))]]
[:& color-inputs
{:type type
:disable-opacity disable-opacity
:color current-color
:on-change handle-change-color}]
[:& color-inputs
{:type type
:disable-opacity disable-opacity
:color current-color
:on-change handle-change-color}]
[:& libraries
{:state state
:current-color current-color
:disable-gradient disable-gradient
:disable-opacity disable-opacity
:disable-image disable-image
:on-select-color on-select-library-color
:on-add-library-color on-add-library-color}]])]
[:& libraries
{:state state
:current-color current-color
:disable-gradient disable-gradient
:disable-opacity disable-opacity
:disable-image disable-image
:on-select-color on-select-library-color
:on-add-library-color on-add-library-color}]])]
[:> token-section* {:combined-tokens combined-tokens
:on-token-change on-token-change
:color-origin color-origin}])]
(when (fn? on-accept)
[:div {:class (stl/css :actions)}
[:button {:class (stl/css-case
@@ -561,6 +599,115 @@
:top top-offset
:maxHeight max-height-top}))))
(defn- group-sets
"Groups sets by their parent path (everything before the last '/') if present.
The set name is always the last part of the path.
Input:
[{:set \"brand/subgroup/one\" :tokens [{:name \"background\"}]}
{:set \"brand/subgroup/two\" :tokens [{:name \"foreground\"}]}
{:set \"primitives\" :tokens [{:name \"blue-100\"}]}]
Output:
[{:group \"brand/subgroup\"
:sets [\"one\" \"two\"]
:tokens [\"background\" \"foreground\"]}
{:group nil
:sets [\"primitives\"]
:tokens [\"blue-100\"]}]"
[sets]
(->> sets
(group-by (fn [{:keys [set]}]
(when (str/includes? set "/")
(str/join "/" (butlast (str/split set #"/"))))))
(map (fn [[group grouped-sets]]
(if group
{:group group
:sets (map #(last (str/split (:set %) #"/")) grouped-sets)
:tokens (->> grouped-sets
(mapcat :tokens)
(map :name)
distinct)}
(map (fn [{:keys [set tokens]}]
{:group nil
:sets [set]
:tokens (map :name tokens)})
grouped-sets))))
flatten))
(defn- combine-groups-with-resolved
"Replaces token names in grouped sets with their full resolved token objects.
Input:
- groups: [{:group \"brand\"
:sets [\"light\" \"dark\"]
:tokens [\"background\" \"foreground\"]} ...]
- resolved-tokens: [{:name \"background\" :type \"color\" :value \"{red-100}\" ...} ...]
Output:
[{:group \"brand\"
:sets [\"light\" \"dark\"]
:tokens [{:name \"background\" :type \"color\" :value \"{red-100}\" ...}
{:name \"foreground\" :type \"color\" :value \"{green-100}\" ...}]}]"
[groups resolved-tokens]
(let [token-map (into {} (map (juxt :name identity) resolved-tokens))]
(map (fn [{:keys [group sets tokens]}]
{:group group
:sets sets
:tokens (->> tokens
(map #(get token-map %))
(remove nil?)
vec)})
groups)))
(defn- filter-non-empty-sets
"Removes sets that have no tokens.
Input:
[{:set \"brand/light\" :tokens []}
{:set \"brand/dark\" :tokens [{:name \"background\"}]}]
Output:
[{:set \"brand/dark\" :tokens [{:name \"background\"}]}]"
[sets]
(filter (fn [{:keys [tokens]}]
(seq tokens))
sets))
(defn- add-tokens-to-sets
"Extracts set name and its tokens from raw set objects.
Input:
A vector of set objects (raw domain type), each compatible with:
{:id ... :name \"brand/light\" :tokens {...}}
Output:
A vector of simplified maps:
[{:set \"brand/light\" :tokens [{:name \"background\" ...} ...]}]"
[sets]
(map (fn [s]
{:set (ctob/get-name s)
:tokens (ctob/get-tokens s)})
sets))
(defn- filter-active-sets
"Filters sets to only include those whose :set value is in active-set-names.
Input:
- sets: [{:set \"brand/light\" :tokens [...]},
{:set \"brand/dark\" :tokens [...]},
{:set \"primitivos\" :tokens [...]},
...]
- active-set-names: #{\"brand/light\" \"primitivos\"}
Output:
[{:set \"brand/light\" :tokens [...]}
{:set \"primitivos\" :tokens [...]}]"
[sets active-set-names]
(filter #(contains? active-set-names (:set %)) sets))
(mf/defc colorpicker-modal
{::mf/register modal/components
::mf/register-as :colorpicker
@@ -569,7 +716,11 @@
disable-gradient
disable-opacity
disable-image
active-tokens
on-change
origin
color-origin
on-token-change
on-close
on-accept]}]
(let [vport (mf/deref viewport)
@@ -587,7 +738,27 @@
(reset! last-change new-data)
(if (fn? on-change)
(on-change new-data)
(st/emit! (dc/update-colorpicker new-data))))))]
(st/emit! (dc/update-colorpicker new-data))))))
tokens-lib
(mf/deref refs/tokens-lib)
active-sets-names
(mf/with-memo [tokens-lib]
(some-> tokens-lib
(ctob/get-active-themes-set-names)))
color-tokens (:color active-tokens)
grouped-tokens-by-set
(mf/with-memo [tokens-lib active-sets-names color-tokens]
(some-> tokens-lib
(ctob/get-sets)
(add-tokens-to-sets)
(filter-active-sets active-sets-names)
(filter-non-empty-sets)
(group-sets)
(combine-groups-with-resolved color-tokens)))]
(mf/with-effect []
(st/emit! (st/emit! (dsc/push-shortcuts ::colorpicker sc/shortcuts)))
@@ -601,8 +772,12 @@
:style style}
[:& colorpicker {:data data
:combined-tokens grouped-tokens-by-set
:disable-gradient disable-gradient
:disable-opacity disable-opacity
:disable-image disable-image
:on-token-change on-token-change
:on-change on-change'
:origin origin
:color-origin color-origin
:on-accept on-accept}]]))

View File

@@ -4,8 +4,11 @@
//
// Copyright (c) KALEIDOS INC
@use "../ds/typography.scss" as t;
@use "../ds/spacing.scss";
@use "../ds/_borders.scss" as *;
@use "../ds/_sizes.scss" as *;
@import "refactor/common-refactor.scss";
@import "../ds/_sizes.scss";
.colorpicker-tooltip {
@extend .modal-background;
@@ -23,22 +26,17 @@
overflow: hidden;
}
.colorpicker-tabs {
padding: 0 var(--sp-m);
}
.top-actions {
display: flex;
align-items: flex-start;
flex-direction: row-reverse;
justify-content: space-between;
height: $s-40;
padding: 0 var(--sp-m);
}
.top-actions-right {
display: flex;
gap: $s-8;
gap: var(--sp-s);
}
.opacity-input-wrapper {
@@ -52,12 +50,12 @@
@include flexCenter;
border-radius: $br-8;
background-color: transparent;
border: $s-1 solid transparent;
height: $s-20;
width: $s-20;
border: $b-1 solid transparent;
height: var(--sp-xl);
width: var(--sp-xl);
border-radius: $br-4;
padding: 0;
margin-top: $s-4;
margin-top: var(--sp-xs);
svg {
@extend .button-icon;
stroke: var(--button-tertiary-foreground-color-rest);
@@ -76,7 +74,7 @@
}
&:active {
outline: none;
border: $s-1 solid transparent;
border: $b-1 solid transparent;
svg {
stroke: var(--button-tertiary-foreground-color-active);
}
@@ -91,17 +89,17 @@
.gradient-buttons {
display: flex;
align-items: center;
gap: $s-8;
gap: var(--sp-s);
}
.gradient-btn {
@extend .button-tertiary;
height: $s-20;
width: $s-20;
height: var(--sp-xl);
width: var(--sp-xl);
border-radius: $br-4;
border: $s-2 solid transparent;
border: $b-2 solid transparent;
&:hover {
border: $s-2 solid var(--colorpicker-details-color-selected);
border: $b-2 solid var(--colorpicker-details-color-selected);
}
}
@@ -109,7 +107,7 @@
background: linear-gradient(180deg, var(--color-foreground-secondary), transparent);
&.selected {
background: linear-gradient(to bottom, rgba(126, 255, 245, 1) 0%, rgba(126, 255, 245, 0.2) 100%);
border: $s-2 solid var(--colorpicker-details-color-selected);
border: $b-2 solid var(--colorpicker-details-color-selected);
}
}
@@ -117,38 +115,38 @@
background: radial-gradient(transparent, var(--color-foreground-secondary));
&.selected {
background: radial-gradient(rgba(126, 255, 245, 1) 0%, rgba(126, 255, 245, 0.2) 100%);
border: $s-2 solid var(--colorpicker-details-color-selected);
border: $b-2 solid var(--colorpicker-details-color-selected);
}
}
.actions {
display: flex;
gap: $s-4;
gap: var(--sp-xs);
}
.accept-color {
@include uppercaseTitleTipography;
@extend .button-primary;
width: 100%;
height: $s-32;
margin-top: $s-8;
height: var(--sp-xxxl);
margin-top: var(--sp-s);
}
.picker-detail-wrapper {
@include flexCenter;
position: relative;
margin: $s-12 0 $s-8 0;
margin: var(--sp-m) 0 var(--sp-s) 0;
}
.center-circle {
width: $s-24;
height: $s-24;
border: $s-2 solid var(--colorpicker-details-color);
width: var(--sp-xxl);
height: var(--sp-xxl);
border: $b-2 solid var(--colorpicker-details-color);
border-radius: $br-circle;
position: absolute;
left: 50%;
top: 50%;
transform: translate(calc(-1 * $s-12), calc(-1 * $s-12));
transform: translate(calc(-1 * var(--sp-m)), calc(-1 * var(--sp-m)));
}
.picker-detail {
@@ -161,7 +159,7 @@
}
.select-image {
margin-top: $s-4;
margin-top: var(--sp-xs);
}
.content {
@@ -172,8 +170,8 @@
background-position: center;
background-size: auto $s-140;
height: $s-140;
margin-bottom: $s-6;
margin-right: $s-1;
margin-bottom: $sz-6;
margin-right: $sz-1;
img {
height: fit-content;
width: fit-content;
@@ -187,11 +185,19 @@
@extend .button-secondary;
@include uppercaseTitleTipography;
width: 100%;
margin-top: $s-12;
height: $s-32;
margin-top: var(--sp-m);
height: var(--sp-xxxl);
}
.checkbox-option {
@extend .input-checkbox;
margin: $s-16 0 0 0;
margin: var(--sp-l) 0 0 0;
}
.token-color-title {
@include t.use-typography("title-small");
color: var(--color-foreground-secondary);
display: flex;
align-items: center;
height: var(--sp-xxxl);
}

View File

@@ -9,7 +9,6 @@
.color-values {
@include flexColumn;
margin-top: $s-8;
padding: 0 var(--sp-m);
&.disable-opacity {
grid-template-columns: 3.5rem repeat(3, 1fr);

View File

@@ -0,0 +1,295 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.colorpicker.color-tokens
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.constants :refer [max-input-length]]
[app.main.data.event :as-alias ev]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.tooltip :refer [tooltip*]]
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.timers :as tm]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc token-empty-state*
{::mf/private true}
[]
[:div {:class (stl/css :color-token-empty-state)}
(tr "color-token.empty-state")])
(mf/defc list-item*
{::mf/private true}
[{:keys [token on-token-pill-click selected] :rest props}]
(let [on-click
(mf/use-fn
(mf/deps token on-token-pill-click)
(fn [event]
(on-token-pill-click event token)))
id-tooltip (mf/use-id)
resolved (:resolved-value token)
color-value (dwta/value->color resolved)]
[:> tooltip* {:id id-tooltip
:style {:width "100%"}
:content (:name token)}
[:button {:class (stl/css-case :color-token-item true
:color-token-selected selected)
:aria-labelledby id-tooltip
:on-click on-click}
[:> swatch* {:background color-value
:tooltip-content (tr "workspace.tokens.resolved-value" resolved)
:size "small"}]
[:div {:class (stl/css :token-name)}
(:name token)]
(when selected
[:> i/icon* {:icon-id i/tick
:size "s"
:class (stl/css :token-selected-icon)}])]]))
(mf/defc grid-item*
{::mf/private true}
[{:keys [token on-token-pill-click selected] :rest props}]
(let [on-click
(mf/use-fn
(mf/deps token on-token-pill-click)
(fn [event]
(on-token-pill-click event token)))
resolved (:resolved-value token)
token-name (:name token)
color-value (dwta/value->color resolved)]
[:div {:class (stl/css-case :color-token-item-grid true
:color-token-selected-grid selected)}
[:> swatch* {:background color-value
:tooltip-content
(mf/html
[:*
[:div (dm/str (tr "workspace.tokens.token-name") ": " token-name)]
[:div (tr "workspace.tokens.resolved-value" resolved)]])
:on-click on-click
:size "medium"}]]))
(defn group->set-name
"Given a group structure, returns a representative set name.
Input:
{:group \"brand\"
:sets [\"light\" \"dark\"]
:tokens [...]}
Output:
- If :group exists → \"brand/light\" (first set in :sets)
- If :group is nil → the first (and only) value of :sets"
[{:keys [group sets]}]
(if group
(str group "/" (first sets))
(first sets)))
(mf/defc set-section*
{::mf/private true}
[{:keys [collapsed toggle-sets-open set name color-origin on-token-change] :rest props}]
(let [list-style* (mf/use-state :list)
list-style (deref list-style*)
toggle-list-style
(mf/use-fn
(mf/deps list-style)
(fn []
(let [new-style (if (= :list list-style) :grid :list)]
(reset! list-style* new-style))))
toggle-set
(mf/use-fn
(mf/deps name toggle-sets-open)
(fn []
(toggle-sets-open name)))
objects (mf/deref refs/workspace-page-objects)
selected (mf/deref refs/selected-shapes)
selected-shapes
(mf/with-memo [selected objects]
(into [] (keep (d/getf objects)) selected))
first-shape (first selected-shapes)
applied-tokens (:applied-tokens first-shape)
has-color-tokens? (get applied-tokens :fill)
has-stroke-tokens? (get applied-tokens :stroke-color)
on-token-pill-click
(mf/use-fn
(mf/deps selected-shapes selected color-origin)
(fn [event token]
(dom/stop-propagation event)
(when (seq selected-shapes)
(if (= :color-selection color-origin)
(on-token-change event token)
(let [attributes (if (= color-origin :stroke) #{:stroke-color} #{:fill})
shape-ids (into #{} (map :id selected-shapes))]
(if (or
(= (:name token) has-stroke-tokens?)
(= (:name token) has-color-tokens?))
(st/emit! (dwta/unapply-token {:attributes attributes
:token token
:shape-ids shape-ids}))
(st/emit! (dwta/apply-token {:shape-ids shape-ids
:attributes attributes
:token token
:on-update-shape dwta/update-fill-stroke}))))))))
create-token-on-set
(mf/use-fn
(mf/deps set)
(fn [_]
(let [first-set-name (group->set-name set)
set-item-id (dm/str "token-set-item-" first-set-name)
set-element (dom/get-element set-item-id)]
(when set-element
(dom/click set-element)
(tm/schedule-on-idle
(let [button-element (dom/get-element "add-token-button-Color")]
#(dom/click button-element)))))))]
[:div {:class (stl/css :color-token-set)}
[:> title-bar* {:collapsable true
:collapsed collapsed
:all-clickable true
:on-collapsed toggle-set
:class (stl/css :set-title-bar)
:title name}
(when (not collapsed)
[:div {:class (stl/css :set-actions)}
[:> icon-button* {:on-click toggle-list-style
:variant "action"
:aria-label (if (= :list list-style)
(tr "workspace.assets.grid-view")
(tr "workspace.assets.list-view"))
:icon (if (= :list list-style)
i/flex-grid
i/view-as-list)}]
[:> icon-button* {:on-click create-token-on-set
:variant "action"
:aria-label (tr "workspace.tokens.add-token" "color")
:icon i/add}]])]
(when (not collapsed)
[:div {:class (stl/css-case :color-token-list true
:list-view (= list-style :list)
:grid-view (= list-style :grid))}
(for [token (:tokens set)]
(let [selected? (if (= color-origin :fill)
(= has-color-tokens? (:name token))
(= has-stroke-tokens? (:name token)))]
(if (= :grid list-style)
[:> grid-item* {:key (str "token-grid-" (:id token))
:on-token-pill-click on-token-pill-click
:selected selected?
:token token}]
[:> list-item* {:key (str "token-list-" (:id token))
:on-token-pill-click on-token-pill-click
:selected selected?
:token token}])))])]))
(defn- label-group-or-set [{:keys [group sets]}]
(if group
(str group " (" (str/join ", " sets) ")")
(first sets)))
(defn- filter-combined-tokens
"Filters the combined-tokens structure by token name.
Removes sets or groups if they end up with no tokens.
Input:
[{:group \"brand\", :sets [\"light\" \"dark\"], :tokens [{:name \"background\"} {:name \"foreground\"}]}
{:group nil, :sets [\"primitivos\"], :tokens [{:name \"blue-100\"} {:name \"red-100\"}]}]
(filter-combined-tokens ... \"blue\")
Output:
[{:group nil, :sets [\"primitivos\"], :tokens [{:name \"blue-100\"}]}]
=> keeps only tokens matching \"blue\", and removes sets/groups if no tokens match."
[combined-tokens term]
(let [term (str/lower (str/trim term))]
(if (str/blank? term)
combined-tokens
(->> combined-tokens
(map (fn [{:keys [tokens] :as entry}]
(let [filtered (filter #(str/includes?
(str/lower (:name %))
term)
tokens)]
(when (seq filtered)
(assoc entry :tokens filtered)))))
(remove nil?)))))
(defn- sort-combined-tokens
"Sorts tokens alphabetically by :name inside each group/set.
Input:
[{:group \"brand\", :sets [\"light\" \"dark\"], :tokens [{:name \"foreground\"} {:name \"background\"}]}]
Output:
[{:group \"brand\", :sets [\"light\" \"dark\"], :tokens [{:name \"background\"} {:name \"foreground\"}]}]"
[combined-tokens]
(map (fn [entry]
(update entry :tokens #(sort-by :name %)))
combined-tokens))
(mf/defc token-section*
{}
[{:keys [combined-tokens color-origin on-token-change] :rest props}]
(let [sets (set (mapv label-group-or-set combined-tokens))
filter-term* (mf/use-state "")
filter-term (deref filter-term*)
open-sets* (mf/use-state sets)
open-sets (deref open-sets*)
toggle-sets-open
(mf/use-fn
(mf/deps open-sets)
(fn [name]
(if (contains? open-sets name)
(swap! open-sets* disj name)
(swap! open-sets* conj name))))
on-filter-tokens
(mf/use-fn
(mf/deps filter-term)
(fn [event]
(let [value (-> event (dom/get-target)
(dom/get-value))]
(reset! filter-term* value))))
filtered-combined (filter-combined-tokens combined-tokens filter-term)
sorted-tokens (sort-combined-tokens filtered-combined)]
(if combined-tokens
[:div {:class (stl/css :color-tokens-section)}
[:> input* {:placeholder "Search by token name"
:icon i/search
:max-length max-input-length
:variant "comfortable"
:class (stl/css :search-input)
:default-value filter-term
:on-change on-filter-tokens}]
(for [combined-sets sorted-tokens]
(let [name (label-group-or-set combined-sets)]
[:> set-section*
{:collapsed (not (contains? open-sets name))
:key (str "set-" name)
:toggle-sets-open toggle-sets-open
:color-origin color-origin
:on-token-change on-token-change
:name name
:set combined-sets}]))]
[:> token-empty-state*])))

View File

@@ -0,0 +1,94 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "../../ds/typography.scss" as t;
@use "../../ds/_borders.scss" as *;
@use "../../ds/_sizes.scss" as *;
.color-token-list {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.color-token-item {
--color-token-background: var(--color-background-primary);
background-color: var(--color-token-background);
color: var(--color-foreground-primary);
text-align: left;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: var(--sp-xs);
width: 100%;
border-radius: $br-8;
padding: var(--sp-xs);
height: var(--sp-xxl);
border: none;
cursor: pointer;
&:hover {
--color-token-background: var(--color-background-tertiary);
}
}
.color-token-empty-state {
@include t.use-typography("body-small");
padding: var(--sp-s) var(--sp-xxl);
text-align: center;
color: var(--color-foreground-secondary);
}
.color-token-selected {
background-color: var(--color-background-tertiary);
}
.color-token-selected-grid {
border: $b-1 solid var(--color-accent-primary);
border-radius: $br-4;
width: fit-content;
}
.token-selected-icon {
color: var(--color-foreground-secondary);
}
.token-name {
@include t.use-typography("body-small");
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.color-tokens-section {
max-height: $sz-430;
overflow: auto;
}
.set-actions {
display: flex;
}
.grid-view {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--sp-xxl), 1fr));
justify-items: start;
gap: var(--sp-s);
}
.list-view {
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.set-title-bar {
@include t.use-typography("title-small");
text-transform: none;
}
.search-input {
padding: $sz-1;
}

View File

@@ -158,6 +158,7 @@
:disable-picker true
:color stop
:index index
:origin :gradient
:on-change handle-change-stop-color
:on-remove handle-remove-stop
:on-focus handle-focus-stop-color

View File

@@ -9,7 +9,6 @@
.libraries {
margin-top: $s-8;
width: 100%;
padding: 0 var(--sp-m);
}
.selected-colors {

View File

@@ -128,6 +128,7 @@
{:x (.-clientX ^js event)
:y (.-clientY ^js event)
:on-accept edit-color
:origin :assets
:data color
:position :right})))
@@ -407,6 +408,7 @@
{:x x-position
:y y-position
:on-accept add-color
:origin :assets
:data {:color "#406280"
:opacity 1}
:position :right})))))

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.main.data.workspace.colors :as dwc]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.hooks :as h]
@@ -34,6 +35,18 @@
(def xf:map-shape-id
(map :shape-id))
(defn- generate-color-operations
[groups old-color prev-colors]
(let [old-color (-> old-color
(dissoc :name :path)
(d/without-nils))
prev-color (d/seek (partial get groups) prev-colors)
color-operations-old (get groups old-color)
color-operations-prev (get groups prev-colors)
color-operations (or color-operations-prev color-operations-old)
old-color (or prev-color old-color)]
[color-operations old-color]))
(mf/defc color-selection-menu*
{::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]}
[{:keys [shapes file-id libraries]}]
@@ -57,22 +70,13 @@
on-change
(mf/use-fn
(fn [new-color old-color from-picker?]
(let [old-color (-> old-color
(dissoc :name :path)
(d/without-nils))
;; When dragging on the color picker sometimes all
(let [;; When dragging on the color picker sometimes all
;; the shapes hasn't updated the color to the prev
;; value so we need this extra calculation
groups (mf/ref-val groups-ref)
prev-colors (mf/ref-val prev-colors-ref)
prev-color (d/seek (partial get groups) prev-colors)
cops-old (get groups old-color)
cops-prev (get groups prev-colors)
cops (or cops-prev cops-old)
old-color (or prev-color old-color)]
[color-operations old-color] (generate-color-operations groups old-color prev-colors)]
(when from-picker?
(let [color (-> new-color
@@ -81,7 +85,7 @@
(mf/set-ref-val! prev-colors-ref
(conj prev-colors color))))
(st/emit! (dwc/change-color-in-selected cops new-color old-color)))))
(st/emit! (dwc/change-color-in-selected color-operations new-color old-color)))))
on-open
(mf/use-fn #(mf/set-ref-val! prev-colors-ref []))
@@ -93,17 +97,31 @@
(mf/use-fn
(fn [color]
(let [groups (mf/ref-val groups-ref)
cops (get groups color)
color-operations (get groups color)
color' (dissoc color :id :file-id)]
(st/emit! (dwc/change-color-in-selected cops color' color)))))
(st/emit! (dwc/change-color-in-selected color-operations color' color)))))
select-only
(mf/use-fn
(fn [color]
(let [groups (mf/ref-val groups-ref)
cops (get groups color)
ids (into (d/ordered-set) xf:map-shape-id cops)]
(st/emit! (dws/select-shapes ids)))))]
color-operations (get groups color)
ids (into (d/ordered-set) xf:map-shape-id color-operations)]
(st/emit! (dws/select-shapes ids)))))
on-token-change
(mf/use-fn
(fn [_ token old-color]
(let [groups (mf/ref-val groups-ref)
prev-colors (mf/ref-val prev-colors-ref)
resolved-value (:resolved-value token)
new-color (dwta/value->color resolved-value)
color (-> new-color
(dissoc :name :path)
(d/without-nils))
[color-operations _] (generate-color-operations groups old-color prev-colors)]
(mf/set-ref-val! prev-colors-ref
(conj prev-colors color))
(st/emit! (dwta/apply-token-on-selected color-operations token)))))]
[:div {:class (stl/css :element-set)}
[:div {:class (stl/css :element-title)}
@@ -132,7 +150,9 @@
:on-detach on-detach
:select-only select-only
:on-change #(on-change %1 color %2)
:on-token-change #(on-token-change %1 %2 color)
:on-open on-open
:origin :color-selection-library
:on-close on-close}]))
(when (and (false? @expand-lib-color) (< 3 (count library-colors)))
[:button {:class (stl/css :more-colors-btn)
@@ -147,6 +167,8 @@
:index index
:select-only select-only
:on-change #(on-change %1 color %2)
:origin :color-selection
:on-token-change #(on-token-change %1 %2 color)
:on-open on-open
:on-close on-close}])

View File

@@ -226,6 +226,7 @@
:on-remove on-remove
:disable-drag disable-drag?
:on-focus on-focus
:origin :fill
:select-on-focus (not disable-drag?)
:on-blur on-blur}]))])

View File

@@ -201,6 +201,7 @@
:title (tr "workspace.options.grid.params.color")
:disable-gradient true
:disable-image true
:origin :guides
:on-change handle-change-color
:on-detach handle-detach-color}]
[:button {:class (stl/css-case :show-more-options true
@@ -242,6 +243,7 @@
:title (tr "workspace.options.grid.params.color")
:disable-gradient true
:disable-image true
:origin :guides
:on-change handle-change-color
:on-detach handle-detach-color}]]]

View File

@@ -232,6 +232,7 @@
:title (tr "workspace.options.shadow-options.color")
:disable-gradient true
:disable-image true
:origin :shadow
:on-change on-update-color
:on-detach on-detach-color
:on-open on-open-row

View File

@@ -50,6 +50,7 @@
:title (tr "workspace.options.canvas-background")
:color color
:on-change on-change
:origin :canvas
:on-open on-open
:on-close on-close}]]]))

View File

@@ -19,6 +19,7 @@
[app.main.ui.components.color-input :refer [color-input*]]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.formats :as fmt]
@@ -47,10 +48,11 @@
(mf/defc color-row*
[{:keys [index color class disable-gradient disable-opacity disable-image disable-picker hidden
on-change on-reorder on-detach on-open on-close on-remove
disable-drag on-focus on-blur select-only select-on-focus]}]
on-change on-reorder on-detach on-open on-close on-remove origin
disable-drag on-focus on-blur select-only select-on-focus on-token-change]}]
(let [libraries (mf/deref refs/files)
on-change (h/use-ref-callback on-change)
on-token-change (h/use-ref-callback on-token-change)
file-id (or (:ref-file color) (:file-id color))
color-id (or (:ref-id color) (:id color))
@@ -70,6 +72,11 @@
class (if (some? class) (dm/str class " ") "")
active-tokens* (mf/use-ctx ctx/active-tokens-by-type)
active-tokens (if active-tokens*
@active-tokens*
{})
opacity?
(and (not multiple-colors?)
(not library-color?)
@@ -133,7 +140,7 @@
handle-click-color
(mf/use-fn
(mf/deps disable-gradient disable-opacity disable-image disable-picker on-change on-close on-open)
(mf/deps disable-gradient disable-opacity disable-image disable-picker on-change on-close on-open active-tokens)
(fn [color event]
(let [color (cond
multiple-colors?
@@ -157,9 +164,13 @@
:disable-image disable-image
;; on-change second parameter means if the source is the color-picker
:on-change #(on-change % index)
:on-token-change on-token-change
:on-close (fn [value opacity id file-id]
(when on-close
(on-close value opacity id file-id)))
:active-tokens active-tokens
:color-origin origin
:origin :sidebar
:data color}]
(when (fn? on-open)

View File

@@ -157,10 +157,11 @@
:on-remove on-remove
:disable-drag disable-drag
:on-focus on-focus
:origin :stroke
:select-on-focus select-on-focus
:on-blur on-blur}]
;; Stroke Width, Alignment & Style
;; Stroke Width, Alignment & Style
[:div {:class (stl/css :stroke-options)}
[:div {:class (stl/css :stroke-width-input-element)
:title (tr "workspace.options.stroke-width")}

View File

@@ -81,15 +81,29 @@
(fn [event]
(dom/stop-propagation event)
(st/emit! (dwtl/set-token-type-section-open type true)
;; FIXME: use dom/get-client-position
(modal/show (:key modal)
{:x (.-clientX ^js event)
:y (.-clientY ^js event)
:position :right
:fields (:fields modal)
:title title
:action "create"
:token-type type}))))
;; Normally the modal position is calculated by client-position,
;; but in some cases it is opened programmatically (not by a user click),
;; so we need to set its position explicitly.
(let [pos (dom/get-client-position event)
window-size (dom/get-window-size)
left-sidebar (dom/get-element "left-sidebar-aside")
x-size (dom/get-data left-sidebar "size")
modal-size {:width 452
:height 392}
x (if (= 0 (:x pos))
(- (int x-size) 30)
(:x pos))
y (if (= 0 (:y pos))
(- (/ (:height window-size) 2) (/ (:height modal-size) 2))
(:y pos))]
(modal/show (:key modal)
{:x x
:y y
:position :right
:fields (:fields modal)
:title title
:action "create"
:token-type type})))))
on-token-pill-click
(mf/use-fn
@@ -111,6 +125,7 @@
[:> icon-button* {:on-click on-popover-open-click
:variant "ghost"
:icon i/add
:id (str "add-token-button-" title)
:aria-label (tr "workspace.tokens.add-token" title)}])]
(when is-open
[:& cmm/asset-section-block {:role :content}

View File

@@ -286,6 +286,7 @@
[:div {:ref dref
:role "button"
:data-testid "tokens-set-item"
:id (str "token-set-item-" (str/join "/" tree-path))
:style {"--tree-depth" tree-depth}
:class (stl/css-case :set-item-container true
:selected-set is-selected

View File

@@ -1972,6 +1972,10 @@ msgstr "Close"
msgid "labels.collapse"
msgstr "Collapse"
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "labels.color"
msgstr "Color"
#: src/app/main/ui/comments.cljs:913
msgid "labels.comment"
msgstr "Comment"
@@ -2777,6 +2781,12 @@ msgstr "Radial"
msgid "media.solid"
msgstr "Solid"
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "color-token.empty-state"
msgstr ""
"No available color tokens. "
"Check active sets/themes or add new tokens."
#: src/app/main/data/common.cljs:128
msgid "modals.add-shared-confirm-empty.hint"
msgstr ""
@@ -7850,6 +7860,10 @@ msgstr "Invalid value: Units are not allowed."
msgid "workspace.tokens.warning-name-change"
msgstr "Renaming this token will break any reference to its old name."
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "workspace.colorpicker.color-tokens"
msgstr "Color tokens"
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:145
msgid "workspace.toolbar.assets"
msgstr "Assets"

View File

@@ -1978,6 +1978,10 @@ msgstr "Cerrar"
msgid "labels.collapse"
msgstr "Colapsar"
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "labels.color"
msgstr "Color"
#: src/app/main/ui/comments.cljs:913
msgid "labels.comment"
msgstr "Comentario"
@@ -2653,6 +2657,10 @@ msgstr "Visibilidad"
msgid "labels.svg"
msgstr "SVG"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:518
msgid "labels.variant"
msgstr "Variante"
#: src/app/main/ui/ds/product/loader.cljs:21
msgid "loader.tips.01.message"
msgstr ""
@@ -2777,6 +2785,12 @@ msgstr "Radial"
msgid "media.solid"
msgstr "Sólido"
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "color-token.empty-state"
msgstr ""
"No hay tokens disponibles. "
"Comprueba los sets/themes activos o crea nuevos tokens de color."
#: src/app/main/data/common.cljs:128
msgid "modals.add-shared-confirm-empty.hint"
msgstr ""
@@ -7737,6 +7751,10 @@ msgstr "Valor no válido: No se permiten unidades."
msgid "workspace.tokens.warning-name-change"
msgstr "Al renombrar este token se romperán las referencias al nombre anterior"
#: src/app/main/ui/workspace/colorpicker.cljs
msgid "workspace.colorpicker.color-tokens"
msgstr "Tokens de color"
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:145
msgid "workspace.toolbar.assets"
msgstr "Recursos"