Merge remote-tracking branch 'origin/token-studio-develop' into token-sets-themes

This commit is contained in:
Florian Schroedl 2024-08-21 11:04:00 +02:00
commit 2df577cba2
11 changed files with 357 additions and 41 deletions

View file

@ -29,6 +29,8 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.editable-select :refer [editable-select]] [app.main.ui.workspace.tokens.editable-select :refer [editable-select]]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token-types :as wtty] [app.main.ui.workspace.tokens.token-types :as wtty]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -985,18 +987,25 @@
(mf/use-fn (mf/use-fn
(mf/deps ids) (mf/deps ids)
(fn [type prop value] (fn [type prop value]
(let [token-value (wtc/maybe-resolve-token-value value) (let [token-identifier (wtt/token-identifier value)
val (or token-value (mth/finite value 0))] val (or token-identifier (mth/finite value 0))
on-update-shape wtch/update-layout-padding]
(cond (cond
(and (= type :simple) (= prop :p1)) (and (= type :simple) (= prop :p1))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val} (if token-identifier
:applied-tokens {:padding-p1 (if token-value (:id value) nil) (st/emit! (wtch/apply-token {:shape-ids ids
:padding-p3 (if token-value (:id value) nil)}})) :attributes #{:p1 :p3}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p1 :p3})))
(and (= type :simple) (= prop :p2)) (and (= type :simple) (= prop :p2))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val} (if token-identifier
:applied-tokens {:padding-p2 (if token-value (:id value) nil) (st/emit! (wtch/apply-token {:shape-ids ids
:padding-p4 (if token-value (:id value) nil)}})) :attributes #{:p2 :p4}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p2 :p4})))
(some? prop) (some? prop)
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}})))))) (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))

View file

@ -136,6 +136,9 @@
(zipmap (repeat value)))] (zipmap (repeat value)))]
{:layout-gap layout-gap})) {:layout-gap layout-gap}))
(defn update-layout-padding [value shape-ids attrs]
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat value))}))
(defn update-layout-spacing [value shape-ids attributes] (defn update-layout-spacing [value shape-ids attributes]
(ptk/reify ::update-layout-spacing (ptk/reify ::update-layout-spacing
ptk/WatchEvent ptk/WatchEvent
@ -148,15 +151,6 @@
(rx/of (rx/of
(dwsl/update-layout layout-shape-ids layout-attributes)))))) (dwsl/update-layout layout-shape-ids layout-attributes))))))
(defn update-layout-spacing-column [value shape-ids]
(ptk/reify ::update-layout-spacing-column
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(for [shape-id shape-ids]
(let [layout-update {:layout-gap {:column-gap value :row-gap value}}]
(dwsl/update-layout [shape-id] layout-update)))))))
(defn update-shape-position [value shape-ids attributes] (defn update-shape-position [value shape-ids attributes]
(ptk/reify ::update-shape-position (ptk/reify ::update-shape-position
ptk/WatchEvent ptk/WatchEvent

View file

@ -81,8 +81,7 @@
(concat [all-action] single-actions))) (concat [all-action] single-actions)))
(defn spacing-attribute-actions [{:keys [token selected-shapes] :as context-data}] (defn spacing-attribute-actions [{:keys [token selected-shapes] :as context-data}]
(let [on-update-shape (fn [resolved-value shape-ids attrs] (let [on-update-shape-padding wtch/update-layout-padding
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat resolved-value))}))
padding-attrs {:p1 "Top" padding-attrs {:p1 "Top"
:p2 "Right" :p2 "Right"
:p3 "Bottom" :p3 "Bottom"
@ -105,7 +104,7 @@
:shape-ids shape-ids}] :shape-ids shape-ids}]
(if all-selected? (if all-selected?
(st/emit! (wtch/unapply-token props)) (st/emit! (wtch/unapply-token props))
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))))} (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-padding))))))}
{:title "Horizontal" {:title "Horizontal"
:selected? horizontal-padding-selected? :selected? horizontal-padding-selected?
:action (fn [] :action (fn []
@ -116,7 +115,7 @@
horizontal-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attributes)) horizontal-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attributes))
:else (wtch/apply-token (assoc props :else (wtch/apply-token (assoc props
:attributes horizontal-attributes :attributes horizontal-attributes
:on-update-shape on-update-shape)))] :on-update-shape on-update-shape-padding)))]
(st/emit! event)))} (st/emit! event)))}
{:title "Vertical" {:title "Vertical"
:selected? vertical-padding-selected? :selected? vertical-padding-selected?
@ -128,7 +127,7 @@
vertical-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes)) vertical-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes))
:else (wtch/apply-token (assoc props :else (wtch/apply-token (assoc props
:attributes vertical-attributes :attributes vertical-attributes
:on-update-shape on-update-shape)))] :on-update-shape on-update-shape-padding)))]
(st/emit! event)))}] (st/emit! event)))}]
single-padding-items (->> padding-attrs single-padding-items (->> padding-attrs
(map (fn [[attr title]] (map (fn [[attr title]]
@ -149,7 +148,7 @@
all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs) all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs)
(wtch/apply-token)) (wtch/apply-token))
selected? (wtch/unapply-token props) selected? (wtch/unapply-token props)
:else (-> (assoc props :on-update-shape on-update-shape) :else (-> (assoc props :on-update-shape on-update-shape-padding)
(wtch/apply-token)))] (wtch/apply-token)))]
(st/emit! event))})))) (st/emit! event))}))))
gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap" gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap"

View file

@ -14,6 +14,7 @@
width: 100%; width: 100%;
padding: $s-8; padding: $s-8;
border-radius: $br-8; border-radius: $br-8;
position: relative;
cursor: pointer; cursor: pointer;
background: transparent; background: transparent;

View file

@ -8,14 +8,15 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
["lodash.debounce" :as debounce] ["lodash.debounce" :as debounce]
[app.main.ui.workspace.tokens.update :as wtu]
[app.common.data :as d] [app.common.data :as d]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.tokens :as dt] [app.main.data.tokens :as dt]
[app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.workspace.tokens.common :as tokens.common] [app.main.ui.workspace.tokens.common :as tokens.common]
[app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.dom :as dom] [app.util.dom :as dom]
[cuerdas.core :as str] [cuerdas.core :as str]
[malli.core :as m] [malli.core :as m]
@ -141,14 +142,15 @@ Token names should only contain letters and digits separated by . characters.")}
(mf/defc form (mf/defc form
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [token token-type] :as _args}] [{:keys [token token-type] :as _args}]
(let [tokens (sd/use-resolved-workspace-tokens) (let [tokens (mf/deref refs/workspace-tokens)
resolved-tokens (sd/use-resolved-tokens tokens)
token-path (mf/use-memo token-path (mf/use-memo
(mf/deps (:name token)) (mf/deps (:name token))
#(wtt/token-name->path (:name token))) #(wtt/token-name->path (:name token)))
tokens-tree (mf/use-memo tokens-tree (mf/use-memo
(mf/deps token-path tokens) (mf/deps token-path resolved-tokens)
(fn [] (fn []
(-> (wtt/token-names-tree tokens) (-> (wtt/token-names-tree resolved-tokens)
;; Allow setting editing token to it's own path ;; Allow setting editing token to it's own path
(d/dissoc-in token-path)))) (d/dissoc-in token-path))))
@ -177,7 +179,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; Value ;; Value
value-ref (mf/use-var (:value token)) value-ref (mf/use-var (:value token))
token-resolve-result (mf/use-state (get-in tokens [(:id token) :resolved-value])) token-resolve-result (mf/use-state (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value]))
set-resolve-value (mf/use-callback set-resolve-value (mf/use-callback
(fn [token-or-err] (fn [token-or-err]
(let [v (cond (let [v (cond
@ -219,7 +221,7 @@ Token names should only contain letters and digits separated by . characters.")}
(not valid-description-field?)) (not valid-description-field?))
on-submit (mf/use-callback on-submit (mf/use-callback
(mf/deps validate-name validate-descripion token tokens) (mf/deps validate-name validate-descripion token resolved-tokens)
(fn [e] (fn [e]
(dom/prevent-default e) (dom/prevent-default e)
;; We have to re-validate the current form values before submitting ;; We have to re-validate the current form values before submitting
@ -236,7 +238,7 @@ Token names should only contain letters and digits separated by . characters.")}
(validate-token-value+ {:input final-value (validate-token-value+ {:input final-value
:name-value final-name :name-value final-name
:token token :token token
:tokens tokens})]) :tokens resolved-tokens})])
(p/finally (fn [result err] (p/finally (fn [result err]
;; The result should be a vector of all resolved validations ;; The result should be a vector of all resolved validations
;; We do not handle the error case as it will be handled by the components validations ;; We do not handle the error case as it will be handled by the components validations

View file

@ -0,0 +1,101 @@
;; 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.tokens.sets
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[okulary.core :as l]
[potok.v2.core :as ptk]
[rumext.v2 :as mf]))
(def active-sets #{#uuid "2858b330-828e-4131-86ed-e4d1c0f4b3e3"
#uuid "d608877b-842a-473b-83ca-b5f8305caf83"})
(def sets-root-order [#uuid "2858b330-828e-4131-86ed-e4d1c0f4b3e3"
#uuid "9c5108aa-bdb4-409c-a3c8-c3dfce2f8bf8"
#uuid "0381446e-1f1d-423f-912c-ab577d61b79b"])
(def sets {#uuid "9c5108aa-bdb4-409c-a3c8-c3dfce2f8bf8" {:type :group
:name "Group A"
:children [#uuid "d1754e56-3510-493f-8287-5ef3417d4141"
#uuid "d608877b-842a-473b-83ca-b5f8305caf83"]}
#uuid "d608877b-842a-473b-83ca-b5f8305caf83" {:type :set
:name "Set A / 1"}
#uuid "d1754e56-3510-493f-8287-5ef3417d4141" {:type :group
:name "Group A / B"
:children [#uuid "f608877b-842a-473b-83ca-b5f8305caf83"
#uuid "7cc05389-9391-426e-bc0e-ba5cb8f425eb"]}
#uuid "f608877b-842a-473b-83ca-b5f8305caf83" {:type :set
:name "Set A / B / 1"}
#uuid "7cc05389-9391-426e-bc0e-ba5cb8f425eb" {:type :set
:name "Set A / B / 2"}
#uuid "2858b330-828e-4131-86ed-e4d1c0f4b3e3" {:type :set
:name "Set Root 1"}
#uuid "0381446e-1f1d-423f-912c-ab577d61b79b" {:type :set
:name "Set Root 2"}})
(def ^:private chevron-icon
(i/icon-xref :arrow (stl/css :chevron-icon)))
(defn set-selected-set
[set-id]
(dm/assert! (uuid? set-id))
(ptk/reify ::set-selected-set
ptk/UpdateEvent
(update [_ state]
(assoc state :selected-set-id set-id))))
(mf/defc sets-tree
[{:keys [selected-set-id set-id]}]
(let [set (get sets set-id)]
(when set
(let [{:keys [type name children]} set
visible? (mf/use-state (contains? active-sets set-id))
collapsed? (mf/use-state false)
icon (if (= type :set) i/document i/group)
selected? (mf/use-state (= set-id selected-set-id))
on-click
(mf/use-fn
(mf/deps type set-id)
(fn [event]
(dom/stop-propagation event)
(st/emit! (set-selected-set set-id))))]
[:div {:class (stl/css :set-item-container)
:on-click on-click}
[:div {:class (stl/css-case :set-item-group (= type :group)
:set-item-set (= type :set)
:selected-set (and (= type :set) @selected?))}
(when (= type :group)
[:span {:class (stl/css-case
:collapsabled-icon true
:collapsed @collapsed?)
:on-click #(when (= type :group) (swap! collapsed? not))}
chevron-icon])
[:span {:class (stl/css :icon)} icon]
[:div {:class (stl/css :set-name)} name]
(when (= type :set)
[:span {:class (stl/css :action-btn)
:on-click #(swap! visible? not)}
(if @visible?
i/shown
i/hide)])]
(when (and children (not @collapsed?))
[:div {:class (stl/css :set-children)}
(for [child-id children]
[:& sets-tree {:key child-id :set-id child-id :selected-set-id selected-set-id}])])]))))
(mf/defc sets-list
[{:keys [selected-set-id]}]
[:ul {:class (stl/css :sets-list)}
(for [set-id sets-root-order]
[:& sets-tree {:key set-id
:set-id set-id
:selected-set-id selected-set-id}])])

View file

@ -0,0 +1,101 @@
// 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
@import "refactor/common-refactor.scss";
.sets-list {
width: 100%;
margin-bottom: $s-12;
overflow-y: auto;
}
.set-item-container {
width: 100%;
cursor: pointer;
color: var(--layer-row-foreground-color);
padding-left: $s-20;
}
.set-item-set,
.set-item-group {
@include bodySmallTypography;
display: flex;
align-items: center;
min-height: $s-32;
width: 100%;
cursor: pointer;
color: var(--layer-row-foreground-color);
.set-name {
@include textEllipsis;
flex-grow: 1;
padding-left: $s-2;
}
.icon {
display: flex;
align-items: center;
width: $s-20;
height: $s-20;
padding-right: $s-4;
svg {
height: $s-20;
width: $s-20;
color: white;
fill: none;
stroke: var(--icon-foreground);
}
}
}
.set-item-set {
&:hover {
background-color: var(--layer-row-background-color-hover);
color: var(--layer-row-foreground-color-hover);
box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover);
}
}
.selected-set {
background-color: var(--layer-row-background-color-selected);
color: var(--layer-row-foreground-color-selected);
box-shadow: -100px 0 0 0 var(--layer-row-background-color-selected);
}
.action-btn {
@extend .button-tertiary;
height: $s-28;
width: $s-28;
svg {
@extend .button-icon;
width: 12px;
height: 12px;
}
}
.collapsabled-icon {
@include buttonStyle;
@include flexCenter;
height: $s-24;
border-radius: $br-8;
--chevron-icon-rotation: 90deg;
&.collapsed {
--chevron-icon-rotation: 0deg;
}
&:hover {
--chevron-icon-color: var(--title-foreground-color-hover);
}
}
.chevron-icon {
@extend .button-icon-small;
margin-right: $s-6;
transform: rotate(var(--chevron-icon-rotation));
stroke: var(--icon-foreground);
}

View file

@ -13,12 +13,14 @@
[app.main.data.tokens :as wdt] [app.main.data.tokens :as wdt]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.assets.common :as cmm] [app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.common :refer [labeled-input]] [app.main.ui.workspace.tokens.common :refer [labeled-input]]
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]] [app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
[app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.sets :refer [sets-list]]
[app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty] [app.main.ui.workspace.tokens.token-types :as wtty]
@ -34,6 +36,9 @@
(def ^:private download-icon (def ^:private download-icon
(i/icon-xref :download (stl/css :download-icon))) (i/icon-xref :download (stl/css :download-icon)))
(def selected-set-id
(l/derived :selected-set-id st/state))
(mf/defc token-pill (mf/defc token-pill
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [on-click token highlighted? on-context-menu]}] [{:keys [on-click token highlighted? on-context-menu]}]
@ -260,13 +265,35 @@
:tokens tokens :tokens tokens
:token-type-props token-type-props}])]])) :token-type-props token-type-props}])]]))
(mf/defc sets-sidebar
[]
(let [selected-set-id (mf/deref selected-set-id)
open? (mf/use-state true)]
[:div {:class (stl/css :sets-sidebar)}
[:div {:class (stl/css :sidebar-header)}
[:& title-bar {:collapsable true
:collapsed (not @open?)
:all-clickable true
:title "SETS"
:on-collapsed #(swap! open? not)}]
[:button {:class (stl/css :add-set)
:on-click #(println "Add Set")}
i/add]]
(when @open?
[:& sets-list {:selected-set-id selected-set-id}])]))
(mf/defc tokens-sidebar-tab (mf/defc tokens-sidebar-tab
{::mf/wrap [mf/memo] {::mf/wrap [mf/memo]
::mf/wrap-props false} ::mf/wrap-props false}
[_props] [_props]
[:div {:class (stl/css :sidebar-tab-wrapper)} (let [show-sets-section? false] ;; temporarily added this variable to see/hide the sets section till we have it working end to end
[:& tokens-explorer] [:div {:class (stl/css :sidebar-tab-wrapper)}
[:button {:class (stl/css :download-json-button) (when show-sets-section?
:on-click wtc/download-tokens-as-json} [:div {:class (stl/css :sets-section-wrapper)}
download-icon [:& sets-sidebar]])
"Export JSON"]]) [:div {:class (stl/css :tokens-section-wrapper)}
[:& tokens-explorer]]
[:button {:class (stl/css :download-json-button)
:on-click wtc/download-tokens-as-json}
download-icon
"Export JSON"]]))

View file

@ -5,11 +5,57 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@import "refactor/common-refactor.scss"; @import "refactor/common-refactor.scss";
@import "./common.scss"; @import "./common.scss";
.sidebar-tab-wrapper { .sidebar-tab-wrapper {
padding: $s-12; display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.sets-section-wrapper {
display: flex;
flex-direction: column;
margin-bottom: $s-8;
}
.sets-sidebar {
position: relative;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-left: $s-8;
padding-top: $s-12;
color: var(--layer-row-foreground-color);
}
.add-set {
@extend .button-tertiary;
height: $s-32;
width: $s-28;
padding: 0;
margin-right: $s-12;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
transform: rotate(90deg);
}
}
.tokens-section-wrapper {
flex: 1;
padding-top: $s-12;
padding-left: $s-12;
overflow-y: auto;
}
// TODO Remove once sets are available to public
.sets-section-wrapper + .tokens-section-wrapper {
padding-top: 0;
} }
.token-pills-wrapper { .token-pills-wrapper {

View file

@ -10,9 +10,8 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def StyleDictionary (def StyleDictionary
"The global StyleDictionary instance used as an external library for now, "Initiates the global StyleDictionary instance with transforms
as the package would need webpack to be bundled, from tokens-studio used to parse and resolved token values."
because shadow-cljs doesn't support some of the more modern bundler features."
(do (do
(sd-transforms/registerTransforms sd) (sd-transforms/registerTransforms sd)
(.registerFormat sd #js {:name "custom/json" (.registerFormat sd #js {:name "custom/json"

View file

@ -0,0 +1,37 @@
(ns token-tests.style-dictionary-test
(:require
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[cljs.test :as t :include-macros true]
[promesa.core :as p]))
(def border-radius-token
{:id #uuid "8c868278-7c8d-431b-bbc9-7d8f15c8edb9"
:value "12px"
:name "borderRadius.sm"
:type :border-radius})
(def reference-border-radius-token
{:id #uuid "b9448d78-fd5b-4e3d-aa32-445904063f5b"
:value "{borderRadius.sm} * 2"
:name "borderRadius.md-with-dashes"
:type :border-radius})
(def tokens {(:id border-radius-token) border-radius-token
(:id reference-border-radius-token) reference-border-radius-token})
(t/deftest resolve-tokens-test
(t/async
done
(t/testing "resolves tokens using style-dictionary"
(-> (sd/resolve-tokens+ tokens)
(p/finally (fn [resolved-tokens]
(let [expected-tokens {"borderRadius.sm"
(assoc border-radius-token
:resolved-value 12
:resolved-unit "px")
"borderRadius.md-with-dashes"
(assoc reference-border-radius-token
:resolved-value 24
:resolved-unit "px")}]
(t/is (= expected-tokens resolved-tokens))
(done))))))))