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.workspace.tokens.core :as wtc]
[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.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@ -985,18 +987,25 @@
(mf/use-fn
(mf/deps ids)
(fn [type prop value]
(let [token-value (wtc/maybe-resolve-token-value value)
val (or token-value (mth/finite value 0))]
(let [token-identifier (wtt/token-identifier value)
val (or token-identifier (mth/finite value 0))
on-update-shape wtch/update-layout-padding]
(cond
(and (= type :simple) (= prop :p1))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}
:applied-tokens {:padding-p1 (if token-value (:id value) nil)
:padding-p3 (if token-value (:id value) nil)}}))
(if token-identifier
(st/emit! (wtch/apply-token {:shape-ids ids
: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))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}
:applied-tokens {:padding-p2 (if token-value (:id value) nil)
:padding-p4 (if token-value (:id value) nil)}}))
(if token-identifier
(st/emit! (wtch/apply-token {:shape-ids ids
:attributes #{:p2 :p4}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p2 :p4})))
(some? prop)
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))

View file

@ -136,6 +136,9 @@
(zipmap (repeat value)))]
{: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]
(ptk/reify ::update-layout-spacing
ptk/WatchEvent
@ -148,15 +151,6 @@
(rx/of
(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]
(ptk/reify ::update-shape-position
ptk/WatchEvent

View file

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

View file

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

View file

@ -8,14 +8,15 @@
(:require-macros [app.main.style :as stl])
(:require
["lodash.debounce" :as debounce]
[app.main.ui.workspace.tokens.update :as wtu]
[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.workspace.tokens.common :as tokens.common]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.dom :as dom]
[cuerdas.core :as str]
[malli.core :as m]
@ -141,14 +142,15 @@ Token names should only contain letters and digits separated by . characters.")}
(mf/defc form
{::mf/wrap-props false}
[{: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
(mf/deps (:name token))
#(wtt/token-name->path (:name token)))
tokens-tree (mf/use-memo
(mf/deps token-path tokens)
(mf/deps token-path resolved-tokens)
(fn []
(-> (wtt/token-names-tree tokens)
(-> (wtt/token-names-tree resolved-tokens)
;; Allow setting editing token to it's own path
(d/dissoc-in token-path))))
@ -177,7 +179,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; Value
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
(fn [token-or-err]
(let [v (cond
@ -219,7 +221,7 @@ Token names should only contain letters and digits separated by . characters.")}
(not valid-description-field?))
on-submit (mf/use-callback
(mf/deps validate-name validate-descripion token tokens)
(mf/deps validate-name validate-descripion token resolved-tokens)
(fn [e]
(dom/prevent-default e)
;; 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
:name-value final-name
:token token
:tokens tokens})])
:tokens resolved-tokens})])
(p/finally (fn [result err]
;; 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

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.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.tokens.changes :as wtch]
[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.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.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty]
@ -34,6 +36,9 @@
(def ^:private 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/wrap-props false}
[{:keys [on-click token highlighted? on-context-menu]}]
@ -260,13 +265,35 @@
:tokens tokens
: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/wrap [mf/memo]
::mf/wrap-props false}
[_props]
[:div {:class (stl/css :sidebar-tab-wrapper)}
[:& tokens-explorer]
[:button {:class (stl/css :download-json-button)
:on-click wtc/download-tokens-as-json}
download-icon
"Export JSON"]])
(let [show-sets-section? false] ;; temporarily added this variable to see/hide the sets section till we have it working end to end
[:div {:class (stl/css :sidebar-tab-wrapper)}
(when show-sets-section?
[:div {:class (stl/css :sets-section-wrapper)}
[:& sets-sidebar]])
[: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
@import "refactor/common-refactor.scss";
@import "./common.scss";
.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 {

View file

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