Merge branch 'develop' into token-studio-develop

This commit is contained in:
Florian Schroedl
2024-10-23 12:05:58 +02:00
26 changed files with 576 additions and 409 deletions

View File

@@ -169,6 +169,7 @@
changes (-> (pcb/empty-changes it)
(pcb/update-token-set token-set prev-token-set))]
(rx/of
(set-selected-token-set-id (:name token-set))
(dch/commit-changes changes))))))
(defn toggle-token-set [{:keys [token-set-name]}]
@@ -258,8 +259,8 @@
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{}))
add-to-hidden-theme? (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)]
(-> base-changes
(pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme)))
(-> base-changes
(pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme)))
:else base-changes))
;; Either update or add token to existing set

View File

@@ -501,7 +501,7 @@
(l/derived
#(some-> (wtts/get-selected-token-set %)
(ctob/get-token token-name))
st/state)))
st/state)))
(def workspace-selected-token-set-tokens
(l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state))

View File

@@ -6,7 +6,6 @@
(ns app.main.ui
(:require
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
[app.config :as cf]
[app.main.refs :as refs]
[app.main.ui.context :as ctx]
@@ -20,6 +19,7 @@
[app.main.ui.onboarding.team-choice :refer [onboarding-team-modal]]
[app.main.ui.releases :refer [release-notes-modal]]
[app.main.ui.static :as static]
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))

View File

@@ -130,3 +130,21 @@
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
}
}
%base-button-action {
--button-bg-color: transparent;
--button-fg-color: var(--color-foreground-secondary);
--button-hover-bg-color: transparent;
--button-hover-fg-color: var(--color-accent-primary);
--button-active-bg-color: var(--color-background-quaternary);
--button-disabled-bg-color: transparent;
--button-disabled-fg-color: var(--color-accent-primary-muted);
--button-focus-bg-color: transparent;
--button-focus-fg-color: var(--color-accent-primary);
--button-focus-inner-ring-color: transparent;
--button-focus-outer-ring-color: var(--color-accent-primary);
}

View File

@@ -12,9 +12,6 @@
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
[rumext.v2 :as mf]))
(def button-variants (set '("primary" "secondary" "ghost" "destructive")))
(def ^:private schema:icon-button
[:map
[:class {:optional true} :string]
@@ -22,7 +19,7 @@
[:and :string [:fn #(contains? icon-list %)]]]
[:aria-label :string]
[:variant {:optional true}
[:maybe [:enum "primary" "secondary" "ghost" "destructive"]]]])
[:maybe [:enum "primary" "secondary" "ghost" "destructive" "action"]]]])
(mf/defc icon-button*
{::mf/props :obj
@@ -33,6 +30,7 @@
:icon-button-primary (= variant "primary")
:icon-button-secondary (= variant "secondary")
:icon-button-ghost (= variant "ghost")
:icon-button-action (= variant "action")
:icon-button-destructive (= variant "destructive")))
props (mf/spread-props props {:class class :title aria-label})]
[:> "button" props [:> icon* {:id icon :aria-label aria-label}]]))

View File

@@ -31,3 +31,7 @@
.icon-button-destructive {
@extend %base-button-destructive;
}
.icon-button-action {
@extend %base-button-action;
}

View File

@@ -26,7 +26,7 @@ export default {
},
disabled: { control: "boolean" },
variant: {
options: ["primary", "secondary", "ghost", "destructive"],
options: ["primary", "secondary", "ghost", "destructive", "action"],
control: { type: "select" },
},
},
@@ -59,6 +59,12 @@ export const Ghost = {
},
};
export const Action = {
args: {
variant: "action",
},
};
export const Destructive = {
args: {
variant: "destructive",

View File

@@ -12,6 +12,7 @@
[app.common.logic.shapes :as cls]
[app.common.types.shape.layout :as ctl]
[app.common.types.shape.radius :as ctsr]
[app.common.types.tokens-lib :as ctob]
[app.main.constants :refer [size-presets]]
[app.main.data.tokens :as dt]
[app.main.data.workspace :as udw]
@@ -32,8 +33,7 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[clojure.set :refer [rename-keys union]]
[rumext.v2 :as mf]
[app.common.types.tokens-lib :as ctob]))
[rumext.v2 :as mf]))
(def measure-attrs
[:proportion-lock
@@ -248,11 +248,11 @@
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwu/start-undo-transaction undo-id)
(dwsh/update-shapes ids
(if token-value
#(assoc-in % [:applied-tokens attr] (:id value))
#(d/dissoc-in % [:applied-tokens attr]))
{:reg-objects? true
:attrs [:applied-tokens]})
(if token-value
#(assoc-in % [:applied-tokens attr] (:id value))
#(d/dissoc-in % [:applied-tokens attr]))
{:reg-objects? true
:attrs [:applied-tokens]})
(udw/update-dimensions ids attr (or token-value value))
(dwu/commit-undo-transaction undo-id)))))

View File

@@ -91,11 +91,11 @@
(defn update-shape-radius-all [value shape-ids]
(dwsh/update-shapes shape-ids
(fn [shape]
(when (ctsr/has-radius? shape)
(ctsr/set-radius-1 shape value)))
{:reg-objects? true
:attrs ctt/border-radius-keys}))
(fn [shape]
(when (ctsr/has-radius? shape)
(ctsr/set-radius-1 shape value)))
{:reg-objects? true
:attrs ctt/border-radius-keys}))
(defn update-opacity [value shape-ids]
(when (<= 0 value 1)
@@ -111,22 +111,22 @@
(defn update-shape-radius-single-corner [value shape-ids attributes]
(dwsh/update-shapes shape-ids
(fn [shape]
(when (ctsr/has-radius? shape)
(cond-> shape
(:rx shape) (ctsr/switch-to-radius-4)
:always (ctsr/set-radius-4 (first attributes) value))))
{:reg-objects? true
:attrs [:rx :ry :r1 :r2 :r3 :r4]}))
(fn [shape]
(when (ctsr/has-radius? shape)
(cond-> shape
(:rx shape) (ctsr/switch-to-radius-4)
:always (ctsr/set-radius-4 (first attributes) value))))
{:reg-objects? true
:attrs [:rx :ry :r1 :r2 :r3 :r4]}))
(defn update-stroke-width
[value shape-ids]
(dwsh/update-shapes shape-ids
(fn [shape]
(when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value)))
{:reg-objects? true
:attrs [:strokes]}))
(fn [shape]
(when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value)))
{:reg-objects? true
:attrs [:strokes]}))
(defn update-color
[value shape-ids]

View File

@@ -47,8 +47,8 @@
:selected? selected?
:action (fn []
(if selected?
(st/emit! (wtch/unapply-token props))
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-fn)))))}))
(st/emit! (wtch/unapply-token props))
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-fn)))))}))
attributes)))
(defn all-or-sepearate-actions [{:keys [attribute-labels on-update-shape-all on-update-shape]}
@@ -216,6 +216,8 @@
:y (.-clientY ^js event)
:position :right
:fields fields
:action "edit"
:selected-token-set-id selected-token-set-id
:token token})))}]))
(defn selection-actions [{:keys [type token] :as context-data}]

View File

@@ -10,12 +10,16 @@
["lodash.debounce" :as debounce]
[app.common.colors :as c]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[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.components.color-bullet :refer [color-bullet]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
[app.main.ui.workspace.tokens.common :as tokens.common]
@@ -25,6 +29,7 @@
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[malli.core :as m]
[malli.error :as me]
@@ -124,7 +129,7 @@ Token names should only contain letters and digits separated by . characters.")}
[name-ref token tokens callback & {:keys [timeout] :or {timeout 160}}]
(let [timeout-id-ref (mf/use-ref nil)
debounced-resolver-callback
(mf/use-callback
(mf/use-fn
(mf/deps token callback tokens)
(fn [value]
(let [timeout-id (js/Symbol)
@@ -178,19 +183,22 @@ Token names should only contain letters and digits separated by . characters.")}
[{:keys [result-or-errors]}]
(let [{:keys [errors]} result-or-errors
empty-message? (or (nil? result-or-errors)
(wte/has-error-code? :error/empty-input errors))]
[:div {:class (stl/css-case :resolved-value true
:resolved-value-placeholder empty-message?
:resolved-value-error (seq errors))}
(cond
empty-message? "Enter token value"
errors (->> (wte/humanize-errors errors)
(str/join "\n"))
:else [:p result-or-errors])]))
(wte/has-error-code? :error/empty-input errors))
message (cond
empty-message? (dm/str (tr "workspace.token.resolved-value") "-")
errors (->> (wte/humanize-errors errors)
(str/join "\n"))
:else (dm/str (tr "workspace.token.resolved-value") result-or-errors))]
[:> text* {:as "p"
:typography "body-small"
:class (stl/css-case :resolved-value true
:resolved-value-placeholder empty-message?
:resolved-value-error (seq errors))}
message]))
(mf/defc form
{::mf/wrap-props false}
[{:keys [token token-type]}]
[{:keys [token token-type action selected-token-set-id]}]
(let [validate-name? (mf/use-state (not (:id token)))
token (or token {:type token-type})
color? (wtt/color-token? token)
@@ -212,25 +220,31 @@ Token names should only contain letters and digits separated by . characters.")}
;; Name
name-ref (mf/use-var (:name token))
name-errors (mf/use-state nil)
validate-name (mf/use-callback
(mf/deps selected-set-tokens-tree)
(fn [value]
(let [schema (token-name-schema {:token token
:tokens-tree selected-set-tokens-tree})]
(m/explain schema (finalize-name value)))))
on-update-name-debounced (mf/use-callback
(debounce (fn [e]
(let [value (dom/get-target-val e)
errors (validate-name value)]
validate-name
(mf/use-fn
(mf/deps selected-set-tokens-tree)
(fn [value]
(let [schema (token-name-schema {:token token
:tokens-tree selected-set-tokens-tree})]
(m/explain schema (finalize-name value)))))
on-update-name-debounced
(mf/use-fn
(debounce (fn [e]
(let [value (dom/get-target-val e)
errors (validate-name value)]
;; Prevent showing error when just going to another field on a new token
(when-not (and validate-name? (str/empty? value))
(reset! validate-name? false)
(reset! name-errors errors))))))
on-update-name (mf/use-callback
(mf/deps on-update-name-debounced)
(fn [e]
(reset! name-ref (dom/get-target-val e))
(on-update-name-debounced e)))
(when-not (and validate-name? (str/empty? value))
(reset! validate-name? false)
(reset! name-errors errors))))))
on-update-name
(mf/use-fn
(mf/deps on-update-name-debounced)
(fn [e]
(reset! name-ref (dom/get-target-val e))
(on-update-name-debounced e)))
valid-name-field? (and
(not @name-errors)
(valid-name? @name-ref))
@@ -241,27 +255,29 @@ Token names should only contain letters and digits separated by . characters.")}
value-input-ref (mf/use-ref nil)
value-ref (mf/use-var (:value token))
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 [error? (:errors token-or-err)
v (if error?
token-or-err
(:resolved-value token-or-err))]
(when color? (reset! color (if error? nil v)))
(reset! token-resolve-result v))))
set-resolve-value
(mf/use-fn
(fn [token-or-err]
(let [error? (:errors token-or-err)
v (if error?
token-or-err
(:resolved-value token-or-err))]
(when color? (reset! color (if error? nil v)))
(reset! token-resolve-result v))))
on-update-value-debounced (use-debonced-resolve-callback name-ref token active-theme-tokens set-resolve-value)
on-update-value (mf/use-callback
on-update-value (mf/use-fn
(mf/deps on-update-value-debounced)
(fn [e]
(let [value (dom/get-target-val e)]
(reset! value-ref value)
(on-update-value-debounced value))))
on-update-color (mf/use-callback
on-update-color (mf/use-fn
(mf/deps on-update-value-debounced)
(fn [hex-value]
(reset! value-ref hex-value)
(set! (.-value (mf/ref-val value-input-ref)) hex-value)
(on-update-value-debounced hex-value)))
value-error? (seq (:errors @token-resolve-result))
valid-value-field? (and
(not value-error?)
@@ -270,17 +286,18 @@ Token names should only contain letters and digits separated by . characters.")}
;; Description
description-ref (mf/use-var (:description token))
description-errors (mf/use-state nil)
validate-descripion (mf/use-callback #(m/explain token-description-schema %))
on-update-description-debounced (mf/use-callback
validate-descripion (mf/use-fn #(m/explain token-description-schema %))
on-update-description-debounced (mf/use-fn
(debounce (fn [e]
(let [value (dom/get-target-val e)
errors (validate-descripion value)]
(reset! description-errors errors)))))
on-update-description (mf/use-callback
(mf/deps on-update-description-debounced)
(fn [e]
(reset! description-ref (dom/get-target-val e))
(on-update-description-debounced e)))
on-update-description
(mf/use-fn
(mf/deps on-update-description-debounced)
(fn [e]
(reset! description-ref (dom/get-target-val e))
(on-update-description-debounced e)))
valid-description-field? (not @description-errors)
;; Form
@@ -288,41 +305,62 @@ Token names should only contain letters and digits separated by . characters.")}
(not valid-value-field?)
(not valid-description-field?))
on-submit (mf/use-callback
(mf/deps validate-name validate-descripion token resolved-tokens)
(fn [e]
(dom/prevent-default e)
on-submit
(mf/use-fn
(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
;; because the validation is asynchronous/debounced
;; and the user might have edited a valid form to make it invalid,
;; and press enter before the next validations could return.
(let [final-name (finalize-name @name-ref)
valid-name?+ (-> (validate-name final-name) schema-validation->promise)
final-value (finalize-value @value-ref)
final-description @description-ref
valid-description?+ (some-> final-description validate-descripion schema-validation->promise)]
(-> (p/all [valid-name?+
valid-description?+
(validate-token-value+ {:value final-value
:name-value final-name
:token token
:tokens resolved-tokens})])
(p/finally (fn [result err]
(let [final-name (finalize-name @name-ref)
valid-name?+ (-> (validate-name final-name) schema-validation->promise)
final-value (finalize-value @value-ref)
final-description @description-ref
valid-description?+ (some-> final-description validate-descripion schema-validation->promise)]
(-> (p/all [valid-name?+
valid-description?+
(validate-token-value+ {:value final-value
:name-value final-name
:token token
: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
(when (and (seq result) (not err))
(st/emit! (dt/update-create-token {:token (ctob/make-token :name final-name
:type (or (:type token) token-type)
:value final-value
:description final-description)
:prev-token-name (:name token)}))
(st/emit! (wtu/update-workspace-tokens))
(modal/hide!))))))))]
[:form
{:class (stl/css :form-wrapper)
:on-submit on-submit}
(when (and (seq result) (not err))
(st/emit! (dt/update-create-token {:token (ctob/make-token :name final-name
:type (or (:type token) token-type)
:value final-value
:description final-description)
:prev-token-name (:name token)}))
(st/emit! (wtu/update-workspace-tokens))
(modal/hide!))))))))
on-delete-token
(mf/use-fn
(mf/deps selected-token-set-id)
(fn [e]
(dom/prevent-default e)
(modal/hide!)
(st/emit! (dt/delete-token selected-token-set-id (:name token)))))
on-cancel
(mf/use-fn
(fn [e]
(dom/prevent-default e)
(modal/hide!)))]
[:form {:class (stl/css :form-wrapper)
:on-submit on-submit}
[:div {:class (stl/css :token-rows)}
[:div
[:> text* {:as "span" :typography "headline-medium"}
(if (= action "edit")
(tr "workspace.token.edit-token")
(tr "workspace.token.create-token" token-type))]
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span "Name"]
[:& tokens.common/labeled-input {:label "Name"
:error? @name-errors
:input-props {:default-value @name-ref
@@ -332,37 +370,59 @@ Token names should only contain letters and digits separated by . characters.")}
(for [error (->> (:errors @name-errors)
(map #(-> (assoc @name-errors :errors [%])
(me/humanize))))]
[:p {:key error
:class (stl/css :error)}
[:> text* {:as "p"
:key error
:typography "body-small"
:class (stl/css :error)}
error])]
[:& tokens.common/labeled-input {:label "Value"
:input-props {:default-value @value-ref
:on-blur on-update-value
:on-change on-update-value
:ref value-input-ref}
:render-right (when color?
(mf/fnc []
[:div {:class (stl/css :color-bullet)
:on-click #(swap! color-ramp-open? not)}
(if-let [hex (some-> @color tinycolor/valid-color tinycolor/->hex)]
[:& color-bullet {:color hex
:mini? true}]
[:div {:class (stl/css :color-bullet-placeholder)}])]))}]
(when @color-ramp-open?
[:& ramp {:color (some-> (or @token-resolve-result (:value token))
(tinycolor/valid-color))
:on-change on-update-color}])
[:& token-value-or-errors {:result-or-errors @token-resolve-result}]
[:div
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span "value"]
[:& tokens.common/labeled-input {:label "Value"
:input-props {:default-value @value-ref
:on-blur on-update-value
:on-change on-update-value
:ref value-input-ref}
:render-right (when color?
(mf/fnc []
[:div {:class (stl/css :color-bullet)
:on-click #(swap! color-ramp-open? not)}
(if-let [hex (some-> @color tinycolor/valid-color tinycolor/->hex)]
[:& color-bullet {:color hex
:mini? true}]
[:div {:class (stl/css :color-bullet-placeholder)}])]))}]
(when @color-ramp-open?
[:& ramp {:color (some-> (or @token-resolve-result (:value token))
(tinycolor/valid-color))
:on-change on-update-color}])
[:& token-value-or-errors {:result-or-errors @token-resolve-result}]]
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span "Description"]
[:& tokens.common/labeled-input {:label "Description"
:input-props {:default-value @description-ref
:on-change on-update-description}}]
(when @description-errors
[:p {:class (stl/css :error)}
[:> text* {:as "p"
:typography "body-small"
:class (stl/css :error)}
(me/humanize @description-errors)])]
[:div {:class (stl/css :button-row)}
[:button {:class (stl/css :button)
:type "submit"
:disabled disabled?}
"Save"]]]]))
[:div {:class (stl/css-case :button-row true
:with-delete (= action "edit"))}
(when (= action "edit")
[:> button* {:on-click on-delete-token
:class (stl/css :delete-btn)
:icon i/delete
:variant "secondary"}
(tr "labels.delete")])
[:> button* {:on-click on-cancel
:variant "secondary"}
(tr "labels.cancel")]
[:> button* {:type "submit"
:variant "primary"
:disabled disabled?}
(tr "labels.save")]]]]))

View File

@@ -8,49 +8,56 @@
@import "./common.scss";
.form-wrapper {
width: $s-260;
width: $s-384;
}
.button-row {
display: flex;
flex-direction: column;
margin-top: $s-16;
display: grid;
grid-template-columns: auto auto;
justify-content: end;
gap: $s-12;
padding-block-start: $s-8;
}
.with-delete {
grid-template-columns: 1fr auto auto;
}
.delete-btn {
justify-self: start;
}
.token-rows {
display: flex;
flex-direction: column;
gap: $s-8;
gap: $s-16;
}
.input-row {
display: flex;
flex-direction: column;
gap: $s-4;
}
.error {
@include bodySmallTypography;
margin-top: $s-6;
padding: $s-4 $s-6;
margin-bottom: 0;
color: var(--status-color-error-500);
}
.resolved-value {
@include bodySmallTypography;
--input-hint-color: var(--color-foreground-primary);
margin-bottom: 0;
padding: $s-4 $s-6;
font-weight: medium;
min-height: 1lh;
color: var(--color-foreground-primary);
border: 1px solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent);
p {
font-size: $fs-12;
margin: 0;
}
color: var(--input-hint-color);
}
.resolved-value-placeholder {
color: var(--color-foreground-secondary);
--input-hint-color: var(--color-foreground-secondary);
}
.resolved-value-error {
color: var(--status-color-error-500);
--input-hint-color: var(--status-color-error-500);
}
.color-bullet {

View File

@@ -8,9 +8,12 @@
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.modal :as modal]
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
[app.main.refs :as refs]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.workspace.tokens.form :refer [form]]
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
[app.util.i18n :refer [tr]]
[okulary.core :as l]
[rumext.v2 :as mf]))
@@ -40,12 +43,21 @@
(mf/defc token-update-create-modal
{::mf/wrap-props false}
[{:keys [x y position token token-type] :as _args}]
(let [wrapper-style (use-viewport-position-style x y position)]
[:div
{:class (stl/css :shadow)
:style wrapper-style}
[{:keys [x y position token token-type action selected-token-set-id] :as _args}]
(let [wrapper-style (use-viewport-position-style x y position)
close-modal (mf/use-fn
(fn []
(modal/hide!)))]
[:div {:class (stl/css :token-modal-wrapper)
:style wrapper-style}
[:> icon-button* {:on-click close-modal
:class (stl/css :close-btn)
:icon i/close
:variant "action"
:aria-label (tr "labels.close")}]
[:& form {:token token
:action action
:selected-token-set-id selected-token-set-id
:token-type token-type}]]))
(mf/defc token-themes-modal

View File

@@ -6,14 +6,19 @@
@import "refactor/common-refactor.scss";
.shadow {
.token-modal-wrapper {
@extend .modal-container-base;
width: auto;
max-width: auto;
min-width: auto;
@include menuShadow;
position: absolute;
width: auto;
min-width: auto;
z-index: 11;
overflow-y: auto;
overflow-x: hidden;
}
.close-btn {
position: absolute;
top: $s-6;
right: $s-6;
}

View File

@@ -165,12 +165,12 @@
:on-change (comp on-update-group dom/get-target-val)}
:render-right (when (seq theme-groups)
(mf/fnc []
[:button {:class (stl/css :group-drop-down-button)
:type "button"
:on-click (fn [e]
(dom/stop-propagation e)
(on-toggle-dropdown))}
i/arrow]))}]]
[:button {:class (stl/css :group-drop-down-button)
:type "button"
:on-click (fn [e]
(dom/stop-propagation e)
(on-toggle-dropdown))}
i/arrow]))}]]
[:& labeled-input {:label "Theme"
:input-props {:default-value (:name theme)
:on-change (comp on-update-name dom/get-target-val)}}]]

View File

@@ -7,10 +7,10 @@
(ns app.main.ui.workspace.tokens.sets
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.notifications :as ntf]
[app.main.data.tokens :as wdt]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.data.notifications :as ntf]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.sets-context :as sets-context]

View File

@@ -28,7 +28,7 @@
[:span {:class (stl/css :title)} title]])
(mf/defc menu
[{:keys [token-set-name]}]
[{:keys [token-set-name]}]
(let [{:keys [on-edit]} (sets-context/use-context)]
[:ul {:class (stl/css :context-list)}
[:& menu-entry {:title "Rename" :on-click #(on-edit token-set-name)}]

View File

@@ -10,8 +10,8 @@
[app.common.data :as d]
[app.common.transit :as t]
[app.common.types.tokens-lib :as ctob]
[app.main.data.notifications :as ntf]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.data.tokens :as dt]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -33,6 +33,7 @@
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@@ -60,8 +61,8 @@
:title (cond
errors? (sd/humanize-errors token)
:else (->> [(str "Token: " name)
(str "Original value: " value)
(str "Resolved value: " resolved-value)]
(str (tr "workspace.token.original-value") value)
(str (tr "workspace.token.resolved-value") resolved-value)]
(str/join "\n")))
:on-click on-click
:on-context-menu on-context-menu
@@ -111,6 +112,7 @@
#(st/emit! (dt/set-token-type-section-open type (not open?))))
on-popover-open-click (mf/use-fn
(fn [event]
(mf/deps type title)
(let [{:keys [key fields]} modal]
(dom/stop-propagation event)
(st/emit! (dt/set-token-type-section-open type true))
@@ -118,6 +120,8 @@
:y (.-clientY ^js event)
:position :right
:fields fields
:title title
:action "create"
:token-type type}))))
on-token-pill-click (mf/use-fn
@@ -175,16 +179,16 @@
:on-click (fn [e]
(dom/stop-propagation e)
(modal/show! :tokens/themes {}))}
(if create? "Create" "Edit")])
(if create? (tr "labels.create") (tr "labels.edit"))])
(mf/defc themes-header
[_props]
(let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)]
[:div {:class (stl/css :themes-wrapper)}
[:span {:class (stl/css :themes-header)} "Themes"]
[:div {:class (stl/css :theme-select-wrapper)}
[:& theme-select]
[:& edit-button {:create? (empty? ordered-themes)}]]]))
[:div {:class (stl/css :themes-wrapper)}
[:span {:class (stl/css :themes-header)} (tr "labels.themes")]
[:div {:class (stl/css :theme-select-wrapper)}
[:& theme-select]
[:& edit-button {:create? (empty? ordered-themes)}]]]))
(mf/defc add-set-button
[{:keys [on-open]}]
@@ -207,7 +211,7 @@
[:& title-bar {:collapsable true
:collapsed (not @open?)
:all-clickable true
:title "SETS"
:title (tr "labels.sets")
:on-collapsed #(swap! open? not)}
[:& add-set-button {:on-open on-open}]]]
(when @open?

View File

@@ -38,7 +38,7 @@
(and (attr? k)
(= v (token-identifier token))))
applied-tokens)
(into {}))))
(into {}))))
(defn token-attribute-applied?
"Test if `token` is applied to a `shape` on single `token-attribute`."

View File

@@ -32,10 +32,10 @@
ctt/rotation-keys wtch/update-rotation})
(def attribute-actions-map
(reduce
(fn [acc [ks action]]
(into acc (map (fn [k] [k action]) ks)))
{} attributes->shape-update))
(reduce
(fn [acc [ks action]]
(into acc (map (fn [k] [k action]) ks)))
{} attributes->shape-update))
;; Helpers ---------------------------------------------------------------------
@@ -110,7 +110,7 @@
(fn [[v shape-ids]]
(action v shape-ids attrs))
update-infos)))
shapes-update-info))
shapes-update-info))
(defn update-tokens [resolved-tokens]
(->> @refs/workspace-page-objects
@@ -124,8 +124,8 @@
(->>
(rx/from
(->
(wtts/get-active-theme-sets-tokens-names-map state)
(wtsd/resolve-tokens+ {:names-map? true})))
(wtts/get-active-theme-sets-tokens-names-map state)
(wtsd/resolve-tokens+ {:names-map? true})))
(rx/mapcat
(fn [sd-tokens]
(let [undo-id (js/Symbol)]

View File

@@ -1,8 +1,8 @@
(ns token-tests.helpers.tokens
(:require
[app.common.test-helpers.ids-map :as thi]
[app.main.ui.workspace.tokens.token :as wtt]
[app.common.types.tokens-lib :as ctob]))
[app.common.types.tokens-lib :as ctob]
[app.main.ui.workspace.tokens.token :as wtt]))
(defn add-token [state label params]
(let [id (thi/new-id! label)

View File

@@ -49,205 +49,205 @@
(t/deftest test-apply-token
(t/testing "applies token to shape and updates shape attributes to resolved value"
(t/async
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.md")
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (:name token)))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.md")
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (:name token)))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
(t/deftest test-apply-multiple-tokens
(t/testing "applying a token twice with the same attributes will override the previously applied tokens values"
(t/async
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.sm")
:on-update-shape wtch/update-shape-radius-all})
(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.md")
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (:name token)))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.sm")
:on-update-shape wtch/update-shape-radius-all})
(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.md")
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (:name token)))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
(t/deftest test-apply-token-overwrite
(t/testing "removes old token attributes and applies only single attribute"
(t/async
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [;; Apply "borderRadius.sm" to all border radius attributes
(wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4}
:token (toht/get-token file "borderRadius.sm")
:shape-ids [(:id rect-1)]
:on-update-shape wtch/update-shape-radius-all})
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [;; Apply "borderRadius.sm" to all border radius attributes
(wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4}
:token (toht/get-token file "borderRadius.sm")
:shape-ids [(:id rect-1)]
:on-update-shape wtch/update-shape-radius-all})
;; Apply single `:r1` attribute to same shape
;; while removing other attributes from the border-radius set
;; but keep `:r4` for testing purposes
(wtch/apply-token {:attributes #{:r1}
:attributes-to-remove #{:rx :ry :r1 :r2 :r3}
:token (toht/get-token file "borderRadius.md")
:shape-ids [(:id rect-1)]
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-sm (toht/get-token file' "borderRadius.sm")
token-md (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "other border-radius attributes got removed"
(t/is (nil? (:rx (:applied-tokens rect-1')))))
(t/testing "r1 got applied with borderRadius.md"
(t/is (= (:r1 (:applied-tokens rect-1')) (:name token-md))))
(t/testing "while :r4 was kept with borderRadius.sm"
(t/is (= (:r4 (:applied-tokens rect-1')) (:name token-sm)))))))))))
(wtch/apply-token {:attributes #{:r1}
:attributes-to-remove #{:rx :ry :r1 :r2 :r3}
:token (toht/get-token file "borderRadius.md")
:shape-ids [(:id rect-1)]
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-sm (toht/get-token file' "borderRadius.sm")
token-md (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "other border-radius attributes got removed"
(t/is (nil? (:rx (:applied-tokens rect-1')))))
(t/testing "r1 got applied with borderRadius.md"
(t/is (= (:r1 (:applied-tokens rect-1')) (:name token-md))))
(t/testing "while :r4 was kept with borderRadius.sm"
(t/is (= (:r4 (:applied-tokens rect-1')) (:name token-sm)))))))))))
(t/deftest test-apply-dimensions
(t/testing "applies dimensions token and updates the shapes width and height"
(t/async
done
(let [dimensions-token {:name "dimensions.sm"
:value "100"
:type :dimensions}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token dimensions-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file "dimensions.sm")
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "dimensions.sm")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
done
(let [dimensions-token {:name "dimensions.sm"
:value "100"
:type :dimensions}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token dimensions-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file "dimensions.sm")
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "dimensions.sm")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
(t/deftest test-apply-sizing
(t/testing "applies sizing token and updates the shapes width and height"
(t/async
done
(let [sizing-token {:name "sizing.sm"
:value "100"
:type :sizing}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token sizing-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file "sizing.sm")
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "sizing.sm")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
done
(let [sizing-token {:name "sizing.sm"
:value "100"
:type :sizing}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token sizing-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file "sizing.sm")
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "sizing.sm")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
(t/deftest test-apply-opacity
(t/testing "applies opacity token and updates the shapes opacity"
(t/async
done
(let [opacity-float {:name "opacity.float"
:value "0.3"
:type :opacity}
opacity-percent {:name "opacity.percent"
:value "40%"
:type :opacity}
opacity-invalid {:name "opacity.invalid"
:value "100"
:type :opacity}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(-> %
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-float))
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-percent))
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-invalid)))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
rect-2 (cths/get-shape file :rect-2)
rect-3 (cths/get-shape file :rect-3)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.float")
:on-update-shape wtch/update-opacity})
(wtch/apply-token {:shape-ids [(:id rect-2)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.percent")
:on-update-shape wtch/update-opacity})
(wtch/apply-token {:shape-ids [(:id rect-3)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.invalid")
:on-update-shape wtch/update-opacity})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
rect-1' (cths/get-shape file' :rect-1)
rect-2' (cths/get-shape file' :rect-2)
rect-3' (cths/get-shape file' :rect-3)
token-opacity-float (toht/get-token file' "opacity.float")
token-opacity-percent (toht/get-token file' "opacity.percent")
token-opacity-invalid (toht/get-token file' "opacity.invalid")]
(t/testing "float value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-1')) (:name token-opacity-float)))
(t/is (= (:opacity rect-1') 0.3)))
(t/testing "percentage value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-2')) (:name token-opacity-percent)))
(t/is (= (:opacity rect-2') 0.4)))
(t/testing "invalid opacity value got applied but did not change shape"
(t/is (= (:opacity (:applied-tokens rect-3')) (:name token-opacity-invalid)))
(t/is (nil? (:opacity rect-3')))))))))))
done
(let [opacity-float {:name "opacity.float"
:value "0.3"
:type :opacity}
opacity-percent {:name "opacity.percent"
:value "40%"
:type :opacity}
opacity-invalid {:name "opacity.invalid"
:value "100"
:type :opacity}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(-> %
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-float))
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-percent))
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-invalid)))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
rect-2 (cths/get-shape file :rect-2)
rect-3 (cths/get-shape file :rect-3)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.float")
:on-update-shape wtch/update-opacity})
(wtch/apply-token {:shape-ids [(:id rect-2)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.percent")
:on-update-shape wtch/update-opacity})
(wtch/apply-token {:shape-ids [(:id rect-3)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.invalid")
:on-update-shape wtch/update-opacity})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
rect-1' (cths/get-shape file' :rect-1)
rect-2' (cths/get-shape file' :rect-2)
rect-3' (cths/get-shape file' :rect-3)
token-opacity-float (toht/get-token file' "opacity.float")
token-opacity-percent (toht/get-token file' "opacity.percent")
token-opacity-invalid (toht/get-token file' "opacity.invalid")]
(t/testing "float value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-1')) (:name token-opacity-float)))
(t/is (= (:opacity rect-1') 0.3)))
(t/testing "percentage value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-2')) (:name token-opacity-percent)))
(t/is (= (:opacity rect-2') 0.4)))
(t/testing "invalid opacity value got applied but did not change shape"
(t/is (= (:opacity (:applied-tokens rect-3')) (:name token-opacity-invalid)))
(t/is (nil? (:opacity rect-3')))))))))))
(t/deftest test-apply-rotation
(t/testing "applies rotation token and updates the shapes rotation"
@@ -278,37 +278,37 @@
(t/deftest test-apply-stroke-width
(t/testing "applies stroke-width token and updates the shapes with stroke"
(t/async
done
(let [stroke-width-token {:name "stroke-width.sm"
:value "10"
:type :stroke-width}
file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner,
:stroke-style :solid,
:stroke-color "#000000",
:stroke-opacity 1,
:stroke-width 5}]}})
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token stroke-width-token))))
store (ths/setup-store file)
rect-with-stroke (cths/get-shape file :rect-1)
rect-without-stroke (cths/get-shape file :rect-2)
events [(wtch/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)]
:attributes #{:stroke-width}
:token (toht/get-token file "stroke-width.sm")
:on-update-shape wtch/update-stroke-width})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "stroke-width.sm")
rect-with-stroke' (cths/get-shape file' :rect-1)
rect-without-stroke' (cths/get-shape file' :rect-2)]
(t/testing "token got applied to rect with stroke and shape stroke got updated"
(t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:name token-target')))
(t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10)))
(t/testing "token got applied to rect without stroke but shape didnt get updated"
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target')))
(t/is (empty? (:strokes rect-without-stroke')))))))))))
done
(let [stroke-width-token {:name "stroke-width.sm"
:value "10"
:type :stroke-width}
file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner,
:stroke-style :solid,
:stroke-color "#000000",
:stroke-opacity 1,
:stroke-width 5}]}})
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token stroke-width-token))))
store (ths/setup-store file)
rect-with-stroke (cths/get-shape file :rect-1)
rect-without-stroke (cths/get-shape file :rect-2)
events [(wtch/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)]
:attributes #{:stroke-width}
:token (toht/get-token file "stroke-width.sm")
:on-update-shape wtch/update-stroke-width})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "stroke-width.sm")
rect-with-stroke' (cths/get-shape file' :rect-1)
rect-without-stroke' (cths/get-shape file' :rect-2)]
(t/testing "token got applied to rect with stroke and shape stroke got updated"
(t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:name token-target')))
(t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10)))
(t/testing "token got applied to rect without stroke but shape didnt get updated"
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target')))
(t/is (empty? (:strokes rect-without-stroke')))))))))))
(t/deftest test-toggle-token-none
(t/testing "should apply token to all selected items, where no item has the token applied"

View File

@@ -1,10 +1,10 @@
(ns token-tests.style-dictionary-test
(:require
[app.common.data :as d]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[cljs.test :as t :include-macros true]
[promesa.core :as p]
[app.main.ui.workspace.tokens.token :as wtt]
[app.common.data :as d]))
[cljs.test :as t :include-macros true]
[promesa.core :as p]))
(def border-radius-token
{:value "12px"

View File

@@ -64,8 +64,8 @@
shape-applied-x-y
shape-applied-all)))
(t/is (= (:y expected) (shape-ids shape-applied-y
shape-applied-x-y
shape-applied-all)))
shape-applied-x-y
shape-applied-all)))
(t/is (= (:z expected) (shape-ids shape-applied-all)))
(t/is (true? (wtt/shapes-applied-all? expected (shape-ids shape-applied-all) attributes)))
(t/is (false? (wtt/shapes-applied-all? expected (apply shape-ids shapes) attributes)))
@@ -77,14 +77,14 @@
(t/testing "is true when single shape matches the token and attributes"
(t/is (true? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "b"}}]
#{:x}))))
#{:x}))))
(t/testing "is false when no shape matches the token or attributes"
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}}
{:applied-tokens {:x "b"}}]
#{:x})))
#{:x})))
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "a"}}]
#{:y})))))
#{:y})))))
(t/deftest name->path-test
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo.bar.baz")))

View File

@@ -1993,6 +1993,14 @@ msgstr "Team Leader"
msgid "labels.team-member"
msgstr "Team member"
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "labels.themes"
msgstr "Themes"
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "labels.sets"
msgstr "Sets"
#: src/app/main/ui/dashboard/sidebar.cljs:992, src/app/main/ui/workspace/main_menu.cljs:118
msgid "labels.tutorials"
msgstr "Tutorials"
@@ -6139,3 +6147,20 @@ msgstr "Update"
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
#: src/app/main/ui/workspace/tokens/form.cljs
msgid "workspace.token.create-token"
msgstr "Create new %s token"
#: src/app/main/ui/workspace/tokens/form.cljs
msgid "workspace.token.edit-token"
msgstr "Edit token"
#: src/app/main/ui/workspace/tokens/form.cljs
msgid "workspace.token.resolved-value"
msgstr "Resolved value: "
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.original-value"
msgstr "Original value: "

View File

@@ -1991,6 +1991,14 @@ msgstr "Líder de equipo"
msgid "labels.team-member"
msgstr "Miembro de equipo"
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "labels.themes"
msgstr "Temas"
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "labels.sets"
msgstr "Sets"
#: src/app/main/ui/dashboard/sidebar.cljs:992, src/app/main/ui/workspace/main_menu.cljs:118
msgid "labels.tutorials"
msgstr "Tutoriales"
@@ -6126,3 +6134,20 @@ msgstr "Pulsar para cerrar la ruta"
msgid "errors.maximum-invitations-by-request-reached"
msgstr "Se ha alcanzado el número máximo (%s) de correos electrónicos que se pueden invitar en una sola solicitud"
#: src/app/main/ui/workspace/tokens/form.cljs
msgid "workspace.token.create-token"
msgstr "Crear un token de %s"
#: src/app/main/ui/workspace/tokens/form.cljs
msgid "workspace.token.edit-token"
msgstr "Editar token"
#: src/app/main/ui/workspace/tokens/form.cljs
msgid "workspace.token.resolved-value"
msgstr "Valor resuelto: "
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.original-value"
msgstr "Valor original: "