mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
Merge branch 'develop' into token-studio-develop
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}]]))
|
||||
@@ -31,3 +31,7 @@
|
||||
.icon-button-destructive {
|
||||
@extend %base-button-destructive;
|
||||
}
|
||||
|
||||
.icon-button-action {
|
||||
@extend %base-button-action;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -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")]]]]))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)}}]]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)}]
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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`."
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")))
|
||||
|
||||
@@ -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: "
|
||||
@@ -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: "
|
||||
Reference in New Issue
Block a user