From ab0219876eb607baa4b2e2543128a8008d196681 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Thu, 5 Jun 2025 09:33:54 +0200 Subject: [PATCH] :sparkles: Add numeric token type (#6575) * :sparkles: Add numeric type token * :bug: Fix comments --- .../app/common/test_helpers/compositions.cljc | 10 + .../src/app/common/test_helpers/shapes.cljc | 2 +- common/src/app/common/text.cljc | 7 + common/src/app/common/types/shape.cljc | 2 +- common/src/app/common/types/token.cljc | 175 ++++++++---------- .../common_tests/types/tokens_lib_test.cljc | 4 +- frontend/resources/images/icons/number.svg | 3 + .../src/app/main/data/style_dictionary.cljs | 44 ++++- .../src/app/main/data/workspace/texts.cljs | 13 +- .../data/workspace/tokens/application.cljs | 81 +++++--- .../main/data/workspace/tokens/errors.cljs | 4 + .../data/workspace/tokens/propagation.cljs | 2 + frontend/src/app/main/ui/ds/_borders.scss | 1 + .../main/ui/ds/foundations/assets/icon.cljs | 1 + frontend/src/app/main/ui/icons.cljs | 1 + .../ui/workspace/tokens/context_menu.cljs | 20 +- .../app/main/ui/workspace/tokens/modals.cljs | 4 +- .../app/main/ui/workspace/tokens/sidebar.cljs | 45 +++-- .../main/ui/workspace/tokens/token_pill.cljs | 25 +-- .../main/ui/workspace/tokens/token_pill.scss | 14 +- .../tokens/logic/token_actions_test.cljs | 37 ++++ frontend/translations/en.po | 8 + frontend/translations/es.po | 8 + 23 files changed, 331 insertions(+), 180 deletions(-) create mode 100644 frontend/resources/images/icons/number.svg diff --git a/common/src/app/common/test_helpers/compositions.cljc b/common/src/app/common/test_helpers/compositions.cljc index ff87d6e15d..fb30bfb595 100644 --- a/common/src/app/common/test_helpers/compositions.cljc +++ b/common/src/app/common/test_helpers/compositions.cljc @@ -29,6 +29,16 @@ :name "Rect1"} params))) +(defn add-text + [file text-label content & {:keys [text-params] :as text}] + (let [shape (-> (cts/setup-shape {:type :text :x 0 :y 0}) + (txt/change-text content))] + (ths/add-sample-shape file text-label + (merge shape + text-params)))) + + + (defn add-frame [file frame-label & {:keys [] :as params}] ;; Generated shape tree: diff --git a/common/src/app/common/test_helpers/shapes.cljc b/common/src/app/common/test_helpers/shapes.cljc index 96746542f3..0479efa082 100644 --- a/common/src/app/common/test_helpers/shapes.cljc +++ b/common/src/app/common/test_helpers/shapes.cljc @@ -133,4 +133,4 @@ (fn [file-data] (ctpl/update-page file-data (:id page) - #(ctst/set-shape % (assoc origin :interactions interactions))))))) + #(ctst/set-shape % (assoc origin :interactions interactions))))))) \ No newline at end of file diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index 5c6bcf3131..0c72896f98 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -169,6 +169,13 @@ item))) root))) +(defn update-text-content + [shape pred-fn update-fn attrs] + (let [update-attrs-fn #(update-fn % attrs) + transform #(transform-nodes pred-fn update-attrs-fn %)] + (-> shape + (update :content transform)))) + (defn generate-shape-name [text] (subs text 0 (min 280 (count text)))) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 3e473df67a..aac6b018e8 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -228,7 +228,7 @@ [:blur {:optional true} ::ctsb/blur] [:grow-type {:optional true} [::sm/one-of grow-types]] - [:applied-tokens {:optional true} ::cto/applied-tokens] + [:applied-tokens {:optional true} cto/schema:applied-tokens] [:plugin-data {:optional true} ::ctpg/plugin-data]]) (def schema:group-attrs diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index e406027d3c..b92b691acc 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.schema :as sm] - [app.common.schema.registry :as sr] [clojure.data :as data] [clojure.set :as set] [malli.util :as mu])) @@ -17,16 +16,10 @@ ;; HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn merge-schemas - "Merge registered schemas." - [& schema-keys] - (let [schemas (map #(get @sr/registry %) schema-keys)] - (reduce sm/merge schemas))) - -(defn schema-keys +(defn- schema-keys "Converts registed map schema into set of keys." - [registered-schema] - (->> (get @sr/registry registered-schema) + [schema] + (->> schema (sm/schema) (mu/keys) (into #{}))) @@ -40,7 +33,7 @@ :border-radius "borderRadius" :color "color" :dimensions "dimension" - :numeric "numeric" + :number "number" :opacity "opacity" :other "other" :rotation "rotation" @@ -55,95 +48,86 @@ (def token-types (into #{} (keys token-type->dtcg-token-type))) -(defn valid-token-type? - [t] - (token-types t)) - (def token-name-ref [:and :string [:re #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?token-attrs ([shape-attr] (shape-attr->token-attrs shape-attr nil)) @@ -197,7 +181,8 @@ (sizing-keys shape-attr) #{shape-attr} (opacity-keys shape-attr) #{shape-attr} (spacing-keys shape-attr) #{shape-attr} - (rotation-keys shape-attr) #{shape-attr}))) + (rotation-keys shape-attr) #{shape-attr} + (number-keys shape-attr) #{shape-attr}))) (defn token-attr->shape-attr [token-attr] diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index 9905a49ff4..d16ea02752 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -29,7 +29,7 @@ :type :boolean :value true) token2 (ctob/make-token :name "test-token-2" - :type :numeric + :type :number :value 66 :description "test description" :modified-at now)] @@ -42,7 +42,7 @@ (t/is (ctob/check-token token1)) (t/is (= (:name token2) "test-token-2")) - (t/is (= (:type token2) :numeric)) + (t/is (= (:type token2) :number)) (t/is (= (:value token2) 66)) (t/is (= (:description token2) "test description")) (t/is (= (:modified-at token2) now)) diff --git a/frontend/resources/images/icons/number.svg b/frontend/resources/images/icons/number.svg new file mode 100644 index 0000000000..1938a596c9 --- /dev/null +++ b/frontend/resources/images/icons/number.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/app/main/data/style_dictionary.cljs b/frontend/src/app/main/data/style_dictionary.cljs index 696ed65388..38a60bb399 100644 --- a/frontend/src/app/main/data/style_dictionary.cljs +++ b/frontend/src/app/main/data/style_dictionary.cljs @@ -55,8 +55,45 @@ {:value value :unit (tinycolor/color-format tc)} {:errors [(wte/error-with-value :error.token/invalid-color value)]})) -(defn- parse-sd-token-numeric-value - "Parses `value` of a numeric `sd-token` into a map like `{:value 1 :unit \"px\"}`. +(defn- numeric-string? [s] + (and (string? s) + (re-matches #"^-?\d+(\.\d+)?$" s))) + +(defn- with-units [s] + (and (string? s) + (re-matches #"^-?\d+(\.\d+)?(px|rem)$" s))) + +;; TODO: After mergin "dimension-tokens" revisit this function to check if it's still +(defn- parse-sd-token-number-value + "Parses `value` of a number `sd-token` into a map like `{:value 1 :unit \"px\"}`. + If the `value` is not parseable and/or has missing references returns a map with `:errors`." + [value] + (let [number? (or (number? value) + (numeric-string? value)) + parsed-value (cft/parse-token-value value) + out-of-bounds (or (>= (:value parsed-value) sm/max-safe-int) + (<= (:value parsed-value) sm/min-safe-int))] + + (cond + (and parsed-value (not out-of-bounds) number?) + parsed-value + + out-of-bounds + {:errors [(wte/error-with-value :error.token/number-too-large value)]} + + (seq (ctob/find-token-value-references value)) + (let [references (seq (ctob/find-token-value-references value))] + {:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] + :references references}) + + (with-units value) + {:errors [(wte/error-with-value :error.style-dictionary/value-with-units value)]} + + :else + {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}))) + +(defn- parse-sd-token-general-value + "Parses `value` of a number `sd-token` into a map like `{:value 1 :unit \"px\"}`. If the `value` is not parseable and/or has missing references returns a map with `:errors`." [value] (let [parsed-value (cft/parse-token-value value) @@ -162,7 +199,8 @@ :color (parse-sd-token-color-value value) :opacity (parse-sd-token-opacity-value value has-references?) :stroke-width (parse-sd-token-stroke-width-value value has-references?) - (parse-sd-token-numeric-value value)) + :number (parse-sd-token-number-value value) + (parse-sd-token-general-value value)) output-token (cond (:errors parsed-token-value) (merge origin-token parsed-token-value) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 53106f25eb..8dc0d22210 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -398,13 +398,6 @@ (rx/of (dwsh/update-shapes shape-ids update-fn)))))) -(defn- update-text-content - [shape pred-fn update-fn attrs] - (let [update-attrs-fn #(update-fn % attrs) - transform #(txt/transform-nodes pred-fn update-attrs-fn %)] - (-> shape - (update :content transform)))) - (defn update-root-attrs [{:keys [id attrs]}] (ptk/reify ::update-root-attrs @@ -416,7 +409,7 @@ update-fn (fn [shape] (if (some? (:content shape)) - (update-text-content shape txt/is-root-node? d/txt-merge attrs) + (txt/update-text-content shape txt/is-root-node? d/txt-merge attrs) (assoc shape :content (d/txt-merge {:type "root"} attrs)))) shape-ids (cond (cfh/text-shape? shape) [id] @@ -444,7 +437,7 @@ node attrs)) - update-fn #(update-text-content % txt/is-paragraph-node? merge-fn attrs) + update-fn #(txt/update-text-content % txt/is-paragraph-node? merge-fn attrs) shape-ids (cond (cfh/text-shape? shape) [id] (cfh/group-shape? shape) (cfh/get-children-ids objects id))] @@ -469,7 +462,7 @@ shape-ids (cond (cfh/text-shape? shape) [id] (cfh/group-shape? shape) (cfh/get-children-ids objects id))] - (rx/of (dwsh/update-shapes shape-ids #(update-text-content % update-node? d/txt-merge attrs)))))))) + (rx/of (dwsh/update-shapes shape-ids #(txt/update-text-content % update-node? d/txt-merge attrs)))))))) (defn migrate-node [node] diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index b807353e20..676f02f53a 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.tokens :as cft] + [app.common.text :as txt] [app.common.types.shape.layout :as ctsl] [app.common.types.shape.radius :as ctsr] [app.common.types.token :as ctt] @@ -42,34 +43,36 @@ (ptk/reify ::apply-token ptk/WatchEvent (watch [_ state _] - (when-let [tokens (some-> (dsh/lookup-file-data state) - (get :tokens-lib) - (ctob/get-active-themes-set-tokens))] - (->> (sd/resolve-tokens tokens) - (rx/mapcat - (fn [resolved-tokens] - (let [undo-id (js/Symbol) - objects (dsh/lookup-page-objects state) + ;; We do not allow to apply tokens while text editor is open. + (when (empty? (get state :workspace-editor-state)) + (when-let [tokens (some-> (dsh/lookup-file-data state) + (get :tokens-lib) + (ctob/get-active-themes-set-tokens))] + (->> (sd/resolve-tokens tokens) + (rx/mapcat + (fn [resolved-tokens] + (let [undo-id (js/Symbol) + objects (dsh/lookup-page-objects state) - shape-ids (or (->> (select-keys objects shape-ids) - (filter (fn [[_ shape]] (not= (:type shape) :group))) - (keys)) - []) + shape-ids (or (->> (select-keys objects shape-ids) + (filter (fn [[_ shape]] (not= (:type shape) :group))) + (keys)) + []) - resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value]) - tokenized-attributes (cft/attributes-map attributes token)] - (rx/of - (st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"})) - (dwu/start-undo-transaction undo-id) - (dwsh/update-shapes shape-ids (fn [shape] - (cond-> shape - attributes-to-remove - (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove)) - :always - (update :applied-tokens merge tokenized-attributes)))) - (when on-update-shape - (on-update-shape resolved-value shape-ids attributes)) - (dwu/commit-undo-transaction undo-id)))))))))) + resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value]) + tokenized-attributes (cft/attributes-map attributes token)] + (rx/of + (st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"})) + (dwu/start-undo-transaction undo-id) + (dwsh/update-shapes shape-ids (fn [shape] + (cond-> shape + attributes-to-remove + (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove)) + :always + (update :applied-tokens merge tokenized-attributes)))) + (when on-update-shape + (on-update-shape resolved-value shape-ids attributes)) + (dwu/commit-undo-transaction undo-id))))))))))) (defn unapply-token "Removes `attributes` that match `token` for `shape-ids`. @@ -328,7 +331,22 @@ (dwsl/update-layout-child shape-ids props {:ignore-touched true :page-id page-id})))))))) -;; Map token types to different properties used along the cokde --------------------------------------------------------- +(defn update-line-height + ([value shape-ids attributes] (update-line-height value shape-ids attributes nil)) + ([value shape-ids _attributes page-id] ; The attributes param is + ; needed to have the same + ; arity that other update + ; functions + (let [update-node? (fn [node] + (or (txt/is-text-node? node) + (txt/is-paragraph-node? node)))] + (when (number? value) + (dwsh/update-shapes shape-ids + #(txt/update-text-content % update-node? d/txt-merge {:line-height value}) + {:ignore-touched true + :page-id page-id}))))) + +;; Map token types to different properties used along the cokde --------------------------------------------- ;; FIXME: the values should be lazy evaluated, probably a function, ;; becasue on future we will need to translate that labels and that @@ -390,6 +408,15 @@ :fields [{:label "Opacity" :key :opacity}]}} + :number + {:title "Number" + :attributes ctt/rotation-keys + :all-attributes ctt/number-keys + :on-update-shape update-rotation + :modal {:key :tokens/number + :fields [{:label "Number" + :key :number}]}} + :rotation {:title "Rotation" :attributes ctt/rotation-keys diff --git a/frontend/src/app/main/data/workspace/tokens/errors.cljs b/frontend/src/app/main/data/workspace/tokens/errors.cljs index 28834bbf77..efe9e2d1c3 100644 --- a/frontend/src/app/main/data/workspace/tokens/errors.cljs +++ b/frontend/src/app/main/data/workspace/tokens/errors.cljs @@ -56,6 +56,10 @@ {:error/code :error.style-dictionary/invalid-token-value :error/fn #(str (tr "workspace.tokens.invalid-value" %))} + :error.style-dictionary/value-with-units + {:error/code :error.style-dictionary/value-with-units + :error/fn #(str (tr "workspace.tokens.value-with-units"))} + :error.style-dictionary/invalid-token-value-opacity {:error/code :error.style-dictionary/invalid-token-value-opacity :error/fn #(str/join "\n" [(str (tr "workspace.tokens.invalid-value" %) ".") (tr "workspace.tokens.opacity-range")])} diff --git a/frontend/src/app/main/data/workspace/tokens/propagation.cljs b/frontend/src/app/main/data/workspace/tokens/propagation.cljs index 6ee22ed9ae..6f3e767fd0 100644 --- a/frontend/src/app/main/data/workspace/tokens/propagation.cljs +++ b/frontend/src/app/main/data/workspace/tokens/propagation.cljs @@ -32,6 +32,7 @@ ctt/stroke-width-keys dwta/update-stroke-width ctt/sizing-keys dwta/update-shape-dimensions ctt/opacity-keys dwta/update-opacity + #{:line-height} dwta/update-line-height #{:x :y} dwta/update-shape-position #{:p1 :p2 :p3 :p4} dwta/update-layout-padding #{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin @@ -125,6 +126,7 @@ (defn- actionize-shapes-update-info [page-id shapes-update-info] (mapcat (fn [[attrs update-infos]] (let [action (some attribute-actions-map attrs)] + (assert (fn? action) "missing action function on attributes->shape-update") (map (fn [[v shape-ids]] (action v shape-ids attrs page-id)) diff --git a/frontend/src/app/main/ui/ds/_borders.scss b/frontend/src/app/main/ui/ds/_borders.scss index d67d4fd802..f6e060d4bd 100644 --- a/frontend/src/app/main/ui/ds/_borders.scss +++ b/frontend/src/app/main/ui/ds/_borders.scss @@ -12,3 +12,4 @@ $br-4: px2rem(4); $br-circle: 50%; $b-1: px2rem(1); +$b-2: px2rem(2); diff --git a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs index a17e5b5bd9..a69d62528d 100644 --- a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs +++ b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs @@ -205,6 +205,7 @@ (def ^:icon-id msg-neutral "msg-neutral") (def ^:icon-id msg-success "msg-success") (def ^:icon-id msg-warning "msg-warning") +(def ^:icon-id number "number") (def ^:icon-id open-link "open-link") (def ^:icon-id padding-bottom "padding-bottom") (def ^:icon-id padding-extended "padding-extended") diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index ed60f89991..0c2c887b50 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -182,6 +182,7 @@ (def ^:icon msg-neutral (icon-xref :msg-neutral)) (def ^:icon msg-success (icon-xref :msg-success)) (def ^:icon msg-warning (icon-xref :msg-warning)) +(def ^:icon number (icon-xref :number)) (def ^:icon open-link (icon-xref :open-link)) (def ^:icon oauth-1 (icon-xref :oauth-1)) (def ^:icon oauth-2 (icon-xref :oauth-2)) diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index bc7b9b5a17..f0889d90aa 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -37,10 +37,14 @@ :selected-pred #(seq (% ids-by-attributes))})) (defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape hint]}] - (let [on-update-shape-fn (or on-update-shape - (-> (dwta/get-token-properties token) - (:on-update-shape))) - {:keys [selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)] + (let [on-update-shape-fn + (or on-update-shape + (-> (dwta/get-token-properties token) + (:on-update-shape))) + + {:keys [selected-pred shape-ids]} + (attribute-actions token selected-shapes attributes)] + (map (fn [attribute] (let [selected? (selected-pred attribute) props {:attributes #{attribute} @@ -244,6 +248,9 @@ :sizing sizing-attribute-actions :rotation (partial generic-attribute-actions #{:rotation} "Rotation") :opacity (partial generic-attribute-actions #{:opacity} "Opacity") + :number (fn [context-data] + [(generic-attribute-actions #{:rotation} "Rotation" (assoc context-data :on-update-shape dwta/update-rotation)) + (generic-attribute-actions #{:line-height} "Line Height" (assoc context-data :on-update-shape dwta/update-line-height))]) :stroke-width stroke-width :dimensions (fn [context-data] (concat @@ -371,9 +378,12 @@ (mf/defc menu-tree [{:keys [selected-shapes submenu-offset type errors] :as context-data}] - (let [shape-types (into #{} (map :type selected-shapes)) + (let [shape-types (into #{} (map :type selected-shapes)) + editing-ref (mf/deref refs/workspace-editor-state) + not-editing? (empty? editing-ref) entries (if (and (not (some? errors)) (seq selected-shapes) + not-editing? (not= shape-types #{:group})) (if (some? type) (submenu-actions-selection-actions context-data) diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.cljs b/frontend/src/app/main/ui/workspace/tokens/modals.cljs index fe7e4504b7..4b96483eb5 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals.cljs @@ -138,9 +138,9 @@ [properties] [:& token-update-create-modal properties]) -(mf/defc numeric-modal +(mf/defc number-modal {::mf/register modal/components - ::mf/register-as :tokens/numeric} + ::mf/register-as :tokens/number} [properties] [:& token-update-create-modal properties]) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index ffeea8a9f6..4c2942797e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -56,6 +56,7 @@ :color "drop" :boolean "boolean-difference" :opacity "percentage" + :number "number" :rotation "rotation" :spacing "padding-extended" :string "text-mixed" @@ -70,6 +71,8 @@ [{:keys [type tokens selected-shapes active-theme-tokens is-open]}] (let [{:keys [modal title]} (get dwta/token-properties type) + editing-ref (mf/deref refs/workspace-editor-state) + not-editing? (empty? editing-ref) can-edit? (mf/use-ctx ctx/can-edit?) @@ -111,10 +114,10 @@ on-token-pill-click (mf/use-fn - (mf/deps selected-shapes) + (mf/deps selected-shapes not-editing?) (fn [event token] (dom/stop-propagation event) - (when (seq selected-shapes) + (when (and not-editing? (seq selected-shapes)) (st/emit! (dwta/toggle-token {:token token :shapes selected-shapes})))))] @@ -129,8 +132,7 @@ [:> icon-button* {:on-click on-popover-open-click :variant "ghost" :icon "add" - ;; TODO: This needs translation - :aria-label (str "Add token: " title)}])] + :aria-label (tr "workspace.tokens.add-token" title)}])] (when is-open [:& cmm/asset-section-block {:role :content} [:div {:class (stl/css :token-pills-wrapper)} @@ -145,22 +147,27 @@ (defn- get-sorted-token-groups "Separate token-types into groups of `empty` or `filled` depending if - tokens exist for that type. Sort each group alphabetically (by - their type)." + tokens exist for that type. Sort each group alphabetically (by their type). + If `:token-units` is not in cf/flags, number tokens are excluded." [tokens-by-type] - (loop [empty #js [] - filled #js [] - types (-> dwta/token-properties keys seq)] - (if-let [type (first types)] - (if (not-empty (get tokens-by-type type)) - (recur empty - (array/conj! filled type) - (rest types)) - (recur (array/conj! empty type) - filled - (rest types))) - [(seq (array/sort! empty)) - (seq (array/sort! filled))]))) + (let [all-types (-> dwta/token-properties keys seq) + token-units? (contains? cf/flags :token-units) + filtered-types (if token-units? + all-types + (remove #(= % :number) all-types))] + (loop [empty #js [] + filled #js [] + types filtered-types] + (if-let [type (first types)] + (if (not-empty (get tokens-by-type type)) + (recur empty + (array/conj! filled type) + (rest types)) + (recur (array/conj! empty type) + filled + (rest types))) + [(seq (array/sort! empty)) + (seq (array/sort! filled))])))) (mf/defc themes-header* {::mf/private true} diff --git a/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs b/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs index f2ebd226d5..1bda279c0e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_pill.cljs @@ -165,14 +165,14 @@ (mf/defc token-pill* {::mf/wrap [mf/memo]} [{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}] - (let [{:keys [name value errors]} token + (let [{:keys [name value errors type]} token has-selected? (pos? (count selected-shapes)) is-reference? (cft/is-reference? token) contains-path? (str/includes? name ".") {:keys [attributes all-attributes]} - (get dwta/token-properties (:type token)) + (get dwta/token-properties type) full-applied? (if has-selected? @@ -204,10 +204,12 @@ color (when (cft/color-token? token) - (let [theme-token (get active-theme-tokens (:name token))] + (let [theme-token (get active-theme-tokens name)] (or (dwtc/resolved-token-bullet-color theme-token) (dwtc/resolved-token-bullet-color token)))) + number-token (= type :number) + on-click (mf/use-fn (mf/deps errors? on-click token) @@ -240,18 +242,20 @@ (dom/stop-propagation event) (when (and can-edit? (not (seq errors)) on-click) (on-click event)))) + on-hover (mf/use-fn (mf/deps selected-shapes is-viewer? active-theme-tokens token half-applied? no-valid-value ref-not-in-active-set) (fn [event] (let [node (dom/get-current-target event) - theme-token (get active-theme-tokens (:name token)) + theme-token (get active-theme-tokens name) title (generate-tooltip is-viewer? (first selected-shapes) theme-token token half-applied? no-valid-value ref-not-in-active-set)] (dom/set-attribute! node "title" title))))] [:button {:class (stl/css-case :token-pill true + :token-pill-no-icon (and number-token (not errors?)) :token-pill-default can-edit? :token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?)) :token-pill-invalid (and can-edit? errors?) @@ -276,13 +280,12 @@ {:icon-id "broken-link" :class (stl/css :token-pill-icon)}] - color - [:& color-bullet {:color color - :mini true}] - :else - [:> token-status-icon* - {:icon-id token-status-id - :class (stl/css :token-pill-icon)}]) + (not number-token) + (if color + [:& color-bullet {:color color :mini true}] + [:> token-status-icon* + {:icon-id token-status-id + :class (stl/css :token-pill-icon)}])) (if contains-path? (let [[first-part last-part] (cfh/split-by-last-period name)] diff --git a/frontend/src/app/main/ui/workspace/tokens/token_pill.scss b/frontend/src/app/main/ui/workspace/tokens/token_pill.scss index 56fcecd5ba..aa6d865deb 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_pill.scss +++ b/frontend/src/app/main/ui/workspace/tokens/token_pill.scss @@ -5,6 +5,7 @@ // Copyright (c) KALEIDOS INC @use "../../ds/typography.scss" as *; +@use "../../ds/borders.scss" as *; @import "refactor/common-refactor.scss"; .token-pill { @@ -16,15 +17,20 @@ grid-template-columns: auto 1fr; align-items: center; gap: $s-6; - border: $s-1 solid var(--token-pill-border); - outline: $s-2 solid var(--token-pill-outline); - height: $s-24; + border: $b-1 solid var(--token-pill-border); + outline: $b-2 solid var(--token-pill-outline); + height: var(--sp-xxl); border-radius: $br-8; - padding: $s-2 $s-8 $s-2 $s-4; + padding: var(--sp-xxs) var(--sp-s) var(--sp-xxs) var(--sp-xs); color: var(--token-pill-foreground); background: var(--token-pill-background); } +.token-pill-no-icon { + grid-template-columns: 1fr; + padding: var(--sp-xxs) var(--sp-s); +} + .name-wrapper { @include use-typography("code-font"); display: block; diff --git a/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs index 8bccd46fb2..6842b24bd9 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_actions_test.cljs @@ -9,9 +9,11 @@ [app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.files :as cthf] [app.common.test-helpers.shapes :as cths] + [app.common.text :as txt] [app.common.types.tokens-lib :as ctob] [app.main.data.workspace.tokens.application :as dwta] [cljs.test :as t :include-macros true] + [cuerdas.core :as str] [frontend-tests.helpers.pages :as thp] [frontend-tests.helpers.state :as ths] [frontend-tests.tokens.helpers.state :as tohs] @@ -39,6 +41,7 @@ (ctho/add-rect :rect-1 rect-1) (ctho/add-rect :rect-2 rect-2) (ctho/add-rect :rect-3 rect-3) + (ctho/add-text :text-1 "Hello World!") (assoc-in [:data :tokens-lib] (-> (ctob/make-tokens-lib) (ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"})) @@ -443,6 +446,40 @@ (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target'))) (t/is (empty? (:strokes rect-without-stroke'))))))))))) +(t/deftest test-apply-line-height + (t/testing "applies line-height token and updates the text line-height" + (t/async + done + (let [line-height-token {:name "big-height" + :value "1.5" + :type :number} + file (-> (setup-file-with-tokens) + (update-in [:data :tokens-lib] + #(ctob/add-token-in-set % "Set A" (ctob/make-token line-height-token)))) + store (ths/setup-store file) + text-1 (cths/get-shape file :text-1) + events [(dwta/apply-token {:shape-ids [(:id text-1)] + :attributes #{:line-height} + :token (toht/get-token file "big-height") + :on-update-shape dwta/update-line-height})]] + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + token-target' (toht/get-token file' "big-height") + text-1' (cths/get-shape file' :text-1) + style-text-blocks (->> (:content text-1') + (txt/content->text+styles) + (remove (fn [[_ text]] (str/empty? (str/trim text)))) + (mapv (fn [[style text]] + {:styles (merge txt/default-text-attrs style) + :text-content text})) + (first) + (:styles))] + (t/is (some? (:applied-tokens text-1'))) + (t/is (= (:line-height (:applied-tokens text-1')) (:name token-target'))) + (t/is (= (:line-height style-text-blocks) 1.5))))))))) + (t/deftest test-toggle-token-none (t/testing "should apply token to all selected items, where no item has the token applied" (t/async diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 0b452e3320..b49b757f73 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -7282,6 +7282,10 @@ msgstr "" msgid "workspace.tokens.invalid-value" msgstr "Invalid token value: %s" +#: src/app/main/data/workspace/tokens/errors.cljs:57 +msgid "workspace.tokens.value-with-units" +msgstr "Invalid value: Units are not allowed." + #: src/app/main/ui/workspace/tokens/modals/themes.cljs:190 msgid "workspace.tokens.label.group" msgstr "Group" @@ -7497,6 +7501,10 @@ msgstr "The value is not valid" msgid "workspace.tokens.warning-name-change" msgstr "Renaming this token will break any reference to its old name." +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "workspace.tokens.add-token" +msgstr "Add token: %s" + #: src/app/main/ui/workspace/sidebar.cljs:137, src/app/main/ui/workspace/sidebar.cljs:146 msgid "workspace.toolbar.assets" msgstr "Assets" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 14e057fadb..0eb403f648 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -7298,6 +7298,10 @@ msgstr "" msgid "workspace.tokens.invalid-value" msgstr "Valor de token no válido: %s" +#: src/app/main/data/workspace/tokens/errors.cljs:57 +msgid "workspace.tokens.value-with-units" +msgstr "Valor no válido: No se permiten unidades." + #: src/app/main/ui/workspace/tokens/modals/themes.cljs:190 msgid "workspace.tokens.label.group" msgstr "Grupo" @@ -7476,6 +7480,10 @@ msgstr "La importación se ha realizado correctamente pero se omitieron algunos msgid "workspace.tokens.unknown-token-type-section" msgstr "El tipo '%s' no está soportado (%s)\n" +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "workspace.tokens.add-token" +msgstr "Añadir token: %s" + #: src/app/main/ui/workspace/sidebar.cljs:137, src/app/main/ui/workspace/sidebar.cljs:146 msgid "workspace.toolbar.assets" msgstr "Recursos"