diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index b406a4472..72f2cb5ad 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -57,15 +57,34 @@ (->> (map (fn [attr] [attr token-id]) attributes) (into {}))) +(defn apply-token-id-to-attributes [{:keys [shape token-id attributes]}] + (let [token (token-from-attributes token-id attributes)] + (toggle-or-apply-token shape token))) + +(defn apply-token-to-shape + [{:keys [shape token attributes] :as _props}] + (let [applied-tokens (apply-token-id-to-attributes {:shape shape + :token-id (:id token) + :attributes attributes})] + (update shape :applied-tokens #(merge % applied-tokens)))) + +(defn maybe-apply-token-to-shape + "When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape." + [{:keys [shape token _attributes] :as props}] + (if token + (apply-token-to-shape props) + shape)) + (defn update-token-from-attributes [{:keys [token-id shape-id attributes]}] (ptk/reify ::update-token-from-attributes ptk/WatchEvent (watch [_ state _] (let [shape (get-shape-from-state shape-id state) - token (token-from-attributes token-id attributes) - next-applied-tokens (toggle-or-apply-token shape token)] - (rx/of (update-shape shape-id {:applied-tokens next-applied-tokens})))))) + applied-tokens (apply-token-id-to-attributes {:shape shape + :token-id token-id + :attributes attributes})] + (rx/of (update-shape shape-id {:applied-tokens applied-tokens})))))) (defn get-token-data-from-token-id [id] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 33e558446..a84a6fe82 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -230,6 +230,12 @@ (def workspace-data (l/derived :workspace-data st/state)) +(def workspace-tokens + (l/derived (fn [data] + (get data :tokens [])) + workspace-data + =)) + (def workspace-file-colors (l/derived (fn [data] (when data diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 13c4ffe53..d457c3d7a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -12,6 +12,7 @@ [app.common.types.shape.layout :as ctl] [app.common.types.shape.radius :as ctsr] [app.main.constants :refer [size-presets]] + [app.main.data.tokens :as dt] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.interactions :as dwi] @@ -19,10 +20,12 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.components.editable-select :refer [editable-select]] [app.main.ui.components.numeric-input :refer [numeric-input*]] [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] + [app.main.ui.workspace.tokens.core :as wtc] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [clojure.set :refer [rename-keys union]] @@ -95,6 +98,10 @@ selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) selection-parents (mf/deref selection-parents-ref) + tokens (mf/deref refs/workspace-tokens) + border-radius-tokens (mf/use-memo (mf/deps tokens) #(wtc/tokens-name-map-for-type :border-radius tokens)) + border-radius-options (mf/use-memo (mf/deps border-radius-tokens) #(map (comp :name val) border-radius-tokens)) + flex-child? (->> selection-parents (some ctl/flex-layout?)) absolute? (ctl/item-absolute? shape) flex-container? (ctl/flex-layout? shape) @@ -255,7 +262,7 @@ (update-fn shape) shape)) {:reg-objects? true - :attrs [:rx :ry :r1 :r2 :r3 :r4]}))) + :attrs [:rx :ry :r1 :r2 :r3 :r4 :applied-tokens]}))) on-switch-to-radius-1 (mf/use-fn @@ -282,9 +289,17 @@ on-radius-1-change (mf/use-fn - (mf/deps ids change-radius) + (mf/deps ids change-radius border-radius-tokens) (fn [value] - (st/emit! (change-radius #(ctsr/set-radius-1 % value))))) + (let [token (when (symbol? value) + (get border-radius-tokens (str value))) + token-value (some-> token wtc/resolve-token-value)] + (st/emit! + (change-radius (fn [shape] + (-> (dt/maybe-apply-token-to-shape {:token token + :shape shape + :attributes (wtc/token-attributes :border-radius)}) + (ctsr/set-radius-1 (or token-value value))))))))) on-radius-multi-change (mf/use-fn @@ -468,12 +483,14 @@ [:div {:class (stl/css :radius-1) :title (tr "workspace.options.radius")} [:span {:class (stl/css :icon)} i/corner-radius] - [:> numeric-input* + [:& editable-select {:placeholder (if (= :multiple (:rx values)) (tr "settings.multiple") "--") - :ref radius-input-ref + :class (stl/css :token-select) + :type "number" :min 0 + :input-class (stl/css :numeric-input) :on-change on-radius-1-change - :className (stl/css :numeric-input) + :options border-radius-options :value (:rx values)}]] @radius-multi? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss index 71fdbefa7..810c79f37 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss @@ -236,3 +236,10 @@ .checkbox-button { @extend .button-icon; } + +.token-select { + li > span { + display: flex; + align-content: center; + } +} diff --git a/frontend/src/app/main/ui/workspace/tokens/core.cljs b/frontend/src/app/main/ui/workspace/tokens/core.cljs index b1e0aabbf..20eebea40 100644 --- a/frontend/src/app/main/ui/workspace/tokens/core.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/core.cljs @@ -37,6 +37,29 @@ int-or-double (throw (ex-info (str "Implement token value resolve for " value) token)))) +(defn group-tokens-by-type + "Groups tokens by their `:type` property." + [tokens] + (->> (vals tokens) + (group-by :type))) + +(defn tokens-name-map + "Convert tokens into a map with their `:name` as the key. + + E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}" + [tokens] + (->> (map (fn [{:keys [name] :as token}] [name token]) tokens) + (into {}))) + +(defn tokens-name-map-for-type + "Convert tokens with `token-type` into a map with their `:name` as the key. + + E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}" + [token-type tokens] + (-> (group-tokens-by-type tokens) + (get token-type []) + (tokens-name-map))) + ;; Update functions ------------------------------------------------------------ (defn on-apply-token [{:keys [token token-type-props selected-shapes] :as _props}] @@ -171,3 +194,6 @@ {:label "Paragraph Indent" :key :paragraph-indent} {:label "Text Decoration" :key :text-decoration} {:label "Text Case" :key :text-case}]}}])) + +(defn token-attributes [token-type] + (get-in token-types [token-type :attributes])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 7e4e306d5..015d2635f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -7,14 +7,14 @@ (ns app.main.ui.workspace.tokens.sidebar (:require-macros [app.main.style :as stl]) (:require + [app.common.data :as d] [app.main.data.modal :as modal] [app.main.data.tokens :as dt] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] - [app.main.ui.workspace.tokens.common :refer [workspace-shapes]] - [app.main.ui.workspace.tokens.core :refer [tokens-applied?] :as wtc] + [app.main.ui.workspace.tokens.core :as wtc] [app.util.dom :as dom] [rumext.v2 :as mf])) @@ -52,7 +52,7 @@ i/add)) (mf/defc token-component - [{:keys [type file tokens selected-shapes token-type-props]}] + [{:keys [type tokens selected-shapes token-type-props]}] (let [open? (mf/use-state false) {:keys [modal attributes title]} token-type-props @@ -87,8 +87,7 @@ :selected-shapes selected-shapes}))) tokens-count (count tokens)] [:div {:on-click on-toggle-open-click} - [:& cmm/asset-section {:file-id (:id file) - :icon (mf/fnc icon-wrapper [_] + [:& cmm/asset-section {:icon (mf/fnc icon-wrapper [_] [:div {:class (stl/css :section-icon)} [:& token-section-icon {:type type}]]) @@ -106,7 +105,7 @@ [:& token-pill {:key (:id token) :token token - :highlighted? (tokens-applied? token selected-shapes attributes) + :highlighted? (wtc/tokens-applied? token selected-shapes attributes) :on-click #(on-token-pill-click % token) :on-context-menu #(on-context-menu % token)}])]])]])) @@ -114,13 +113,12 @@ "Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type. Sort each group alphabetically (by their `:token-key`)." [tokens] - (let [tokens-by-group (->> (vals tokens) - (group-by :type)) + (let [tokens-by-type (wtc/group-tokens-by-type tokens) {:keys [empty filled]} (->> wtc/token-types (map (fn [[token-key token-type-props]] {:token-key token-key :token-type-props token-type-props - :tokens (get tokens-by-group token-key [])})) + :tokens (get tokens-by-type token-key [])})) (group-by (fn [{:keys [tokens]}] (if (empty? tokens) :empty :filled))))] {:empty (sort-by :token-key empty) @@ -128,21 +126,20 @@ (mf/defc tokens-explorer [_props] - (let [file (mf/deref refs/workspace-file) - current-page-id (:current-page-id @st/state) - workspace-data (mf/deref refs/workspace-data) - tokens (get workspace-data :tokens) + (let [objects (mf/deref refs/workspace-page-objects) + + selected (mf/deref refs/selected-shapes) + selected-shapes (into [] (keep (d/getf objects)) selected) + + tokens (mf/deref refs/workspace-tokens) token-groups (mf/with-memo [tokens] - (sorted-token-groups tokens)) - selected-shape-ids (mf/deref refs/selected-shapes) - selected-shapes (workspace-shapes workspace-data current-page-id selected-shape-ids)] + (sorted-token-groups tokens))] [:article [:div.assets-bar (for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups) (:empty token-groups))] [:& token-component {:key token-key :type token-key - :file file :selected-shapes selected-shapes :tokens tokens :token-type-props token-type-props}])]]))