mirror of
https://github.com/penpot/penpot.git
synced 2025-05-17 20:16:11 +02:00
✨ Implement token import / export
This commit is contained in:
parent
41dc6083cf
commit
c6ed081a0b
16 changed files with 1248 additions and 810 deletions
|
@ -26,8 +26,6 @@
|
||||||
[app.common.types.token :as cto]
|
[app.common.types.token :as cto]
|
||||||
[app.common.types.token-theme :as ctot]
|
[app.common.types.token-theme :as ctot]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.common.types.tokens-list :as ctol]
|
|
||||||
[app.common.types.tokens-theme-list :as ctotl]
|
|
||||||
[app.common.types.typographies-list :as ctyl]
|
[app.common.types.typographies-list :as ctyl]
|
||||||
[app.common.types.typography :as ctt]
|
[app.common.types.typography :as ctt]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
|
@ -306,6 +304,11 @@
|
||||||
[:type [:= :del-token-set]]
|
[:type [:= :del-token-set]]
|
||||||
[:name :string]]]
|
[:name :string]]]
|
||||||
|
|
||||||
|
[:set-tokens-lib
|
||||||
|
[:map {:title "SetTokensLib"}
|
||||||
|
[:type [:= :set-tokens-lib]]
|
||||||
|
[:tokens-lib :any]]]
|
||||||
|
|
||||||
[:add-token
|
[:add-token
|
||||||
[:map {:title "AddTokenChange"}
|
[:map {:title "AddTokenChange"}
|
||||||
[:type [:= :add-token]]
|
[:type [:= :add-token]]
|
||||||
|
@ -792,6 +795,10 @@
|
||||||
|
|
||||||
;; -- Tokens
|
;; -- Tokens
|
||||||
|
|
||||||
|
(defmethod process-change :set-tokens-lib
|
||||||
|
[data {:keys [tokens-lib]}]
|
||||||
|
(assoc data :tokens-lib tokens-lib))
|
||||||
|
|
||||||
(defmethod process-change :add-token
|
(defmethod process-change :add-token
|
||||||
[data {:keys [set-name token]}]
|
[data {:keys [set-name token]}]
|
||||||
(update data :tokens-lib #(-> %
|
(update data :tokens-lib #(-> %
|
||||||
|
|
|
@ -771,6 +771,15 @@
|
||||||
(update :undo-changes conj {:type :move-token-set-before :set-name set-name :before-set-name prev-before-set-name})
|
(update :undo-changes conj {:type :move-token-set-before :set-name set-name :before-set-name prev-before-set-name})
|
||||||
(apply-changes-local)))
|
(apply-changes-local)))
|
||||||
|
|
||||||
|
(defn set-tokens-lib
|
||||||
|
[changes tokens-lib]
|
||||||
|
(let [library-data (::library-data (meta changes))
|
||||||
|
prev-tokens-lib (get library-data :tokens-lib)]
|
||||||
|
(-> changes
|
||||||
|
(update :redo-changes conj {:type :set-tokens-lib :tokens-lib tokens-lib})
|
||||||
|
(update :undo-changes conj {:type :set-tokens-lib :tokens-lib prev-tokens-lib})
|
||||||
|
(apply-changes-local))))
|
||||||
|
|
||||||
(defn add-token
|
(defn add-token
|
||||||
[changes set-name token]
|
[changes set-name token]
|
||||||
(-> changes
|
(-> changes
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.schema.registry :as sr]
|
[app.common.schema.registry :as sr]
|
||||||
|
[clojure.set :as set]
|
||||||
[malli.util :as mu]))
|
[malli.util :as mu]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -32,21 +33,26 @@
|
||||||
;; SCHEMA
|
;; SCHEMA
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def token-type->dtcg-token-type
|
||||||
|
{:boolean "boolean"
|
||||||
|
:border-radius "borderRadius"
|
||||||
|
:box-shadow "boxShadow"
|
||||||
|
:color "color"
|
||||||
|
:dimensions "dimension"
|
||||||
|
:numeric "numeric"
|
||||||
|
:opacity "opacity"
|
||||||
|
:other "other"
|
||||||
|
:rotation "rotation"
|
||||||
|
:sizing "sizing"
|
||||||
|
:spacing "spacing"
|
||||||
|
:string "string"
|
||||||
|
:stroke-width "strokeWidth"})
|
||||||
|
|
||||||
|
(def dtcg-token-type->token-type
|
||||||
|
(set/map-invert token-type->dtcg-token-type))
|
||||||
|
|
||||||
(def token-types
|
(def token-types
|
||||||
#{:boolean
|
(into #{} (keys token-type->dtcg-token-type)))
|
||||||
:border-radius
|
|
||||||
:box-shadow
|
|
||||||
:color
|
|
||||||
:dimensions
|
|
||||||
:numeric
|
|
||||||
:opacity
|
|
||||||
:other
|
|
||||||
:rotation
|
|
||||||
:sizing
|
|
||||||
:spacing
|
|
||||||
:string
|
|
||||||
:stroke-width
|
|
||||||
:typography})
|
|
||||||
|
|
||||||
(defn valid-token-type?
|
(defn valid-token-type?
|
||||||
[t]
|
[t]
|
||||||
|
|
|
@ -99,6 +99,14 @@
|
||||||
|
|
||||||
;; === Token
|
;; === Token
|
||||||
|
|
||||||
|
(def token-separator ".")
|
||||||
|
|
||||||
|
(defn get-token-path [path]
|
||||||
|
(get-path path token-separator))
|
||||||
|
|
||||||
|
(defn split-token-path [path]
|
||||||
|
(split-path path token-separator))
|
||||||
|
|
||||||
(defrecord Token [name type value description modified-at])
|
(defrecord Token [name type value description modified-at])
|
||||||
|
|
||||||
(def schema:token
|
(def schema:token
|
||||||
|
@ -178,12 +186,25 @@
|
||||||
(defn split-token-set-path [path]
|
(defn split-token-set-path [path]
|
||||||
(split-path path set-separator))
|
(split-path path set-separator))
|
||||||
|
|
||||||
|
(defn tokens-tree
|
||||||
|
"Convert tokens into a nested tree with their `:name` as the path.
|
||||||
|
Optionally use `update-token-fn` option to transform the token."
|
||||||
|
[tokens & {:keys [update-token-fn]
|
||||||
|
:or {update-token-fn identity}}]
|
||||||
|
(reduce
|
||||||
|
(fn [acc [_ token]]
|
||||||
|
(let [path (split-token-path (:name token))]
|
||||||
|
(assoc-in acc path (update-token-fn token))))
|
||||||
|
{} tokens))
|
||||||
|
|
||||||
(defprotocol ITokenSet
|
(defprotocol ITokenSet
|
||||||
(add-token [_ token] "add a token at the end of the list")
|
(add-token [_ token] "add a token at the end of the list")
|
||||||
(update-token [_ token-name f] "update a token in the list")
|
(update-token [_ token-name f] "update a token in the list")
|
||||||
(delete-token [_ token-name] "delete a token from the list")
|
(delete-token [_ token-name] "delete a token from the list")
|
||||||
(get-token [_ token-name] "return token by token-name")
|
(get-token [_ token-name] "return token by token-name")
|
||||||
(get-tokens [_] "return an ordered sequence of all tokens in the set"))
|
(get-tokens [_] "return an ordered sequence of all tokens in the set")
|
||||||
|
(get-tokens-tree [_] "returns a tree of tokens split & nested by their name path")
|
||||||
|
(get-dtcg-tokens-tree [_] "returns tokens tree formated to the dtcg spec"))
|
||||||
|
|
||||||
(defrecord TokenSet [name description modified-at tokens]
|
(defrecord TokenSet [name description modified-at tokens]
|
||||||
ITokenSet
|
ITokenSet
|
||||||
|
@ -219,7 +240,16 @@
|
||||||
(get tokens token-name))
|
(get tokens token-name))
|
||||||
|
|
||||||
(get-tokens [_]
|
(get-tokens [_]
|
||||||
(vals tokens)))
|
(vals tokens))
|
||||||
|
|
||||||
|
(get-tokens-tree [_]
|
||||||
|
(tokens-tree tokens))
|
||||||
|
|
||||||
|
(get-dtcg-tokens-tree [_]
|
||||||
|
(tokens-tree tokens :update-token-fn (fn [token]
|
||||||
|
(cond-> {"$value" (:value token)
|
||||||
|
"$type" (cto/token-type->dtcg-token-type (:type token))}
|
||||||
|
(:description token) (assoc "$description" (:description token)))))))
|
||||||
|
|
||||||
(def schema:token-set
|
(def schema:token-set
|
||||||
[:and [:map {:title "TokenSet"}
|
[:and [:map {:title "TokenSet"}
|
||||||
|
@ -440,6 +470,31 @@ When `before-set-name` is nil, move set to bottom")
|
||||||
(def valid-active-token-themes?
|
(def valid-active-token-themes?
|
||||||
(sm/validator schema:active-token-themes))
|
(sm/validator schema:active-token-themes))
|
||||||
|
|
||||||
|
;; === Import / Export from DTCG format
|
||||||
|
|
||||||
|
(defn flatten-nested-tokens-json
|
||||||
|
"Recursively flatten the dtcg token structure, joining keys with '.'."
|
||||||
|
[tokens token-path]
|
||||||
|
(reduce-kv
|
||||||
|
(fn [acc k v]
|
||||||
|
(let [child-path (if (empty? token-path)
|
||||||
|
(name k)
|
||||||
|
(str token-path "." k))]
|
||||||
|
(if (and (map? v)
|
||||||
|
(not (contains? v "$type")))
|
||||||
|
(merge acc (flatten-nested-tokens-json v child-path))
|
||||||
|
(let [token-type (cto/dtcg-token-type->token-type (get v "$type"))]
|
||||||
|
(if token-type
|
||||||
|
(assoc acc child-path (make-token
|
||||||
|
:name child-path
|
||||||
|
:type token-type
|
||||||
|
:value (get v "$value")
|
||||||
|
:description (get v "$description")))
|
||||||
|
;; Discard unknown tokens
|
||||||
|
acc)))))
|
||||||
|
{}
|
||||||
|
tokens))
|
||||||
|
|
||||||
;; === Tokens Lib
|
;; === Tokens Lib
|
||||||
|
|
||||||
(defprotocol ITokensLib
|
(defprotocol ITokensLib
|
||||||
|
@ -451,6 +506,8 @@ When `before-set-name` is nil, move set to bottom")
|
||||||
(get-active-themes-set-names [_] "set of set names that are active in the the active themes")
|
(get-active-themes-set-names [_] "set of set names that are active in the the active themes")
|
||||||
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes")
|
(get-active-themes-set-tokens [_] "set of set names that are active in the the active themes")
|
||||||
(update-set-name [_ old-set-name new-set-name] "updates set name in themes")
|
(update-set-name [_ old-set-name new-set-name] "updates set name in themes")
|
||||||
|
(encode-dtcg [_] "Encodes library to a dtcg compatible json string")
|
||||||
|
(decode-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library")
|
||||||
(validate [_]))
|
(validate [_]))
|
||||||
|
|
||||||
(deftype TokensLib [sets set-groups themes active-themes]
|
(deftype TokensLib [sets set-groups themes active-themes]
|
||||||
|
@ -724,6 +781,25 @@ When `before-set-name` is nil, move set to bottom")
|
||||||
themes)
|
themes)
|
||||||
active-themes))
|
active-themes))
|
||||||
|
|
||||||
|
(encode-dtcg [_]
|
||||||
|
(into {} (map (fn [[k v]]
|
||||||
|
[k (get-dtcg-tokens-tree v)])
|
||||||
|
sets)))
|
||||||
|
|
||||||
|
(decode-dtcg-json [_ parsed-json]
|
||||||
|
(let [token-sets (into (d/ordered-map)
|
||||||
|
(map (fn [[set-name tokens]]
|
||||||
|
[set-name (make-token-set
|
||||||
|
:name set-name
|
||||||
|
:tokens (flatten-nested-tokens-json tokens ""))]))
|
||||||
|
(-> parsed-json
|
||||||
|
;; tokens-studio/plugin will add these meta properties, remove them for now
|
||||||
|
(dissoc "$themes" "$metadata")))]
|
||||||
|
(TokensLib. token-sets
|
||||||
|
set-groups
|
||||||
|
themes
|
||||||
|
active-themes)))
|
||||||
|
|
||||||
(validate [_]
|
(validate [_]
|
||||||
(and (valid-token-sets? sets) ;; TODO: validate set-groups
|
(and (valid-token-sets? sets) ;; TODO: validate set-groups
|
||||||
(valid-token-themes? themes)
|
(valid-token-themes? themes)
|
||||||
|
|
|
@ -0,0 +1,803 @@
|
||||||
|
{
|
||||||
|
"core": {
|
||||||
|
"dimension": {
|
||||||
|
"scale": {
|
||||||
|
"$value": "2",
|
||||||
|
"$type": "dimension"
|
||||||
|
},
|
||||||
|
"xs": {
|
||||||
|
"$value": "4",
|
||||||
|
"$type": "dimension"
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"$value": "{dimension.xs} * {dimension.scale}",
|
||||||
|
"$type": "dimension"
|
||||||
|
},
|
||||||
|
"md": {
|
||||||
|
"$value": "{dimension.sm} * {dimension.scale}",
|
||||||
|
"$type": "dimension"
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"$value": "{dimension.md} * {dimension.scale}",
|
||||||
|
"$type": "dimension"
|
||||||
|
},
|
||||||
|
"xl": {
|
||||||
|
"$value": "{dimension.lg} * {dimension.scale}",
|
||||||
|
"$type": "dimension"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"xs": {
|
||||||
|
"$value": "{dimension.xs}",
|
||||||
|
"$type": "spacing"
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"$value": "{dimension.sm}",
|
||||||
|
"$type": "spacing"
|
||||||
|
},
|
||||||
|
"md": {
|
||||||
|
"$value": "{dimension.md}",
|
||||||
|
"$type": "spacing"
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"$value": "{dimension.lg}",
|
||||||
|
"$type": "spacing"
|
||||||
|
},
|
||||||
|
"xl": {
|
||||||
|
"$value": "{dimension.xl}",
|
||||||
|
"$type": "spacing"
|
||||||
|
},
|
||||||
|
"multi-value": {
|
||||||
|
"$value": "{dimension.sm} {dimension.xl}",
|
||||||
|
"$type": "spacing",
|
||||||
|
"$description": "You can have multiple values in a single spacing token"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"sm": {
|
||||||
|
"$value": "4",
|
||||||
|
"$type": "borderRadius"
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"$value": "8",
|
||||||
|
"$type": "borderRadius"
|
||||||
|
},
|
||||||
|
"xl": {
|
||||||
|
"$value": "16",
|
||||||
|
"$type": "borderRadius"
|
||||||
|
},
|
||||||
|
"multi-value": {
|
||||||
|
"$value": "{borderRadius.sm} {borderRadius.lg}",
|
||||||
|
"$type": "borderRadius",
|
||||||
|
"$description": "You can have multiple values in a single radius token. Read more on these: https://docs.tokens.studio/available-tokens/border-radius-tokens#single--multiple-values"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"black": {
|
||||||
|
"$value": "#000000",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"white": {
|
||||||
|
"$value": "#ffffff",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"gray": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#f7fafc",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#edf2f7",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#e2e8f0",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#cbd5e0",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#a0aec0",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#718096",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#4a5568",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#2d3748",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#1a202c",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"red": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#fff5f5",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#fed7d7",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#feb2b2",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#fc8181",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#f56565",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#e53e3e",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#c53030",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#9b2c2c",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#742a2a",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"orange": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#fffaf0",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#feebc8",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#fbd38d",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#f6ad55",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#ed8936",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#dd6b20",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#c05621",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#9c4221",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#7b341e",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yellow": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#fffff0",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#fefcbf",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#faf089",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#f6e05e",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#ecc94b",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#d69e2e",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#b7791f",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#975a16",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#744210",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"green": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#f0fff4",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#c6f6d5",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#9ae6b4",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#68d391",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#48bb78",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#38a169",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#2f855a",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#276749",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#22543d",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"teal": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#e6fffa",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#b2f5ea",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#81e6d9",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#4fd1c5",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#38b2ac",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#319795",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#2c7a7b",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#285e61",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#234e52",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blue": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#ebf8ff",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#bee3f8",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#90cdf4",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#63b3ed",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#4299e1",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#3182ce",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#2b6cb0",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#2c5282",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#2a4365",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indigo": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#ebf4ff",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#c3dafe",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#a3bffa",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#7f9cf5",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#667eea",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#5a67d8",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#4c51bf",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#434190",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#3c366b",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"purple": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#faf5ff",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#e9d8fd",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#d6bcfa",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#b794f4",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#9f7aea",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#805ad5",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#6b46c1",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#553c9a",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#44337a",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pink": {
|
||||||
|
"100": {
|
||||||
|
"$value": "#fff5f7",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"200": {
|
||||||
|
"$value": "#fed7e2",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"300": {
|
||||||
|
"$value": "#fbb6ce",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$value": "#f687b3",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$value": "#ed64a6",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"600": {
|
||||||
|
"$value": "#d53f8c",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"700": {
|
||||||
|
"$value": "#b83280",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"800": {
|
||||||
|
"$value": "#97266d",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"900": {
|
||||||
|
"$value": "#702459",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"low": {
|
||||||
|
"$value": "10%",
|
||||||
|
"$type": "opacity"
|
||||||
|
},
|
||||||
|
"md": {
|
||||||
|
"$value": "50%",
|
||||||
|
"$type": "opacity"
|
||||||
|
},
|
||||||
|
"high": {
|
||||||
|
"$value": "90%",
|
||||||
|
"$type": "opacity"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fontFamilies": {
|
||||||
|
"heading": {
|
||||||
|
"$value": "Inter",
|
||||||
|
"$type": "fontFamilies"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"$value": "Roboto",
|
||||||
|
"$type": "fontFamilies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lineHeights": {
|
||||||
|
"heading": {
|
||||||
|
"$value": "110%",
|
||||||
|
"$type": "lineHeights"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"$value": "140%",
|
||||||
|
"$type": "lineHeights"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"letterSpacing": {
|
||||||
|
"default": {
|
||||||
|
"$value": "0",
|
||||||
|
"$type": "letterSpacing"
|
||||||
|
},
|
||||||
|
"increased": {
|
||||||
|
"$value": "150%",
|
||||||
|
"$type": "letterSpacing"
|
||||||
|
},
|
||||||
|
"decreased": {
|
||||||
|
"$value": "-5%",
|
||||||
|
"$type": "letterSpacing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paragraphSpacing": {
|
||||||
|
"h1": {
|
||||||
|
"$value": "32",
|
||||||
|
"$type": "paragraphSpacing"
|
||||||
|
},
|
||||||
|
"h2": {
|
||||||
|
"$value": "26",
|
||||||
|
"$type": "paragraphSpacing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fontWeights": {
|
||||||
|
"headingRegular": {
|
||||||
|
"$value": "Regular",
|
||||||
|
"$type": "fontWeights"
|
||||||
|
},
|
||||||
|
"headingBold": {
|
||||||
|
"$value": "Bold",
|
||||||
|
"$type": "fontWeights"
|
||||||
|
},
|
||||||
|
"bodyRegular": {
|
||||||
|
"$value": "Regular",
|
||||||
|
"$type": "fontWeights"
|
||||||
|
},
|
||||||
|
"bodyBold": {
|
||||||
|
"$value": "Bold",
|
||||||
|
"$type": "fontWeights"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fontSizes": {
|
||||||
|
"h1": {
|
||||||
|
"$value": "{fontSizes.h2} * 1.25",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"h2": {
|
||||||
|
"$value": "{fontSizes.h3} * 1.25",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"h3": {
|
||||||
|
"$value": "{fontSizes.h4} * 1.25",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"h4": {
|
||||||
|
"$value": "{fontSizes.h5} * 1.25",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"h5": {
|
||||||
|
"$value": "{fontSizes.h6} * 1.25",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"h6": {
|
||||||
|
"$value": "{fontSizes.body} * 1",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"$value": "16",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"$value": "{fontSizes.body} * 0.85",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
},
|
||||||
|
"xs": {
|
||||||
|
"$value": "{fontSizes.body} * 0.65",
|
||||||
|
"$type": "fontSizes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"light": {
|
||||||
|
"fg": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.black}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"muted": {
|
||||||
|
"$value": "{colors.gray.700}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"subtle": {
|
||||||
|
"$value": "{colors.gray.500}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bg": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.white}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"muted": {
|
||||||
|
"$value": "{colors.gray.100}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"subtle": {
|
||||||
|
"$value": "{colors.gray.200}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.indigo.400}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"onAccent": {
|
||||||
|
"$value": "{colors.white}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"bg": {
|
||||||
|
"$value": "{colors.indigo.200}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shadows": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.gray.900}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dark": {
|
||||||
|
"fg": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.white}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"muted": {
|
||||||
|
"$value": "{colors.gray.300}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"subtle": {
|
||||||
|
"$value": "{colors.gray.500}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bg": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.gray.900}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"muted": {
|
||||||
|
"$value": "{colors.gray.700}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"subtle": {
|
||||||
|
"$value": "{colors.gray.600}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accent": {
|
||||||
|
"default": {
|
||||||
|
"$value": "{colors.indigo.600}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"onAccent": {
|
||||||
|
"$value": "{colors.white}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"bg": {
|
||||||
|
"$value": "{colors.indigo.800}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shadows": {
|
||||||
|
"default": {
|
||||||
|
"$value": "rgba({colors.black}, 0.3)",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"button": {
|
||||||
|
"primary": {
|
||||||
|
"background": {
|
||||||
|
"$value": "{accent.default}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"$value": "{accent.onAccent}",
|
||||||
|
"$type": "color"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"borderRadius": {
|
||||||
|
"$value": "{borderRadius.lg}",
|
||||||
|
"$type": "borderRadius"
|
||||||
|
},
|
||||||
|
"borderWidth": {
|
||||||
|
"$value": "{dimension.sm}",
|
||||||
|
"$type": "borderWidth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"borderRadius": {
|
||||||
|
"$value": "{borderRadius.lg}",
|
||||||
|
"$type": "borderRadius"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"$value": "{bg.default}",
|
||||||
|
"$type": "color"
|
||||||
|
},
|
||||||
|
"padding": {
|
||||||
|
"$value": "{dimension.md}",
|
||||||
|
"$type": "dimension"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boxShadow": {
|
||||||
|
"default": {
|
||||||
|
"$value": [
|
||||||
|
{
|
||||||
|
"x": 5,
|
||||||
|
"y": 5,
|
||||||
|
"spread": 3,
|
||||||
|
"color": "rgba({shadows.default}, 0.15)",
|
||||||
|
"blur": 5,
|
||||||
|
"$type": "dropShadow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 4,
|
||||||
|
"y": 4,
|
||||||
|
"spread": 6,
|
||||||
|
"color": "#00000033",
|
||||||
|
"blur": 5,
|
||||||
|
"$type": "innerShadow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "boxShadow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"H1": {
|
||||||
|
"Bold": {
|
||||||
|
"$value": {
|
||||||
|
"fontFamily": "{fontFamilies.heading}",
|
||||||
|
"fontWeight": "{fontWeights.headingBold}",
|
||||||
|
"lineHeight": "{lineHeights.heading}",
|
||||||
|
"fontSize": "{fontSizes.h1}",
|
||||||
|
"paragraphSpacing": "{paragraphSpacing.h1}",
|
||||||
|
"letterSpacing": "{letterSpacing.decreased}"
|
||||||
|
},
|
||||||
|
"$type": "typography"
|
||||||
|
},
|
||||||
|
"Regular": {
|
||||||
|
"$value": {
|
||||||
|
"fontFamily": "{fontFamilies.heading}",
|
||||||
|
"fontWeight": "{fontWeights.headingRegular}",
|
||||||
|
"lineHeight": "{lineHeights.heading}",
|
||||||
|
"fontSize": "{fontSizes.h1}",
|
||||||
|
"paragraphSpacing": "{paragraphSpacing.h1}",
|
||||||
|
"letterSpacing": "{letterSpacing.decreased}"
|
||||||
|
},
|
||||||
|
"$type": "typography"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"H2": {
|
||||||
|
"Bold": {
|
||||||
|
"$value": {
|
||||||
|
"fontFamily": "{fontFamilies.heading}",
|
||||||
|
"fontWeight": "{fontWeights.headingBold}",
|
||||||
|
"lineHeight": "{lineHeights.heading}",
|
||||||
|
"fontSize": "{fontSizes.h2}",
|
||||||
|
"paragraphSpacing": "{paragraphSpacing.h2}",
|
||||||
|
"letterSpacing": "{letterSpacing.decreased}"
|
||||||
|
},
|
||||||
|
"$type": "typography"
|
||||||
|
},
|
||||||
|
"Regular": {
|
||||||
|
"$value": {
|
||||||
|
"fontFamily": "{fontFamilies.heading}",
|
||||||
|
"fontWeight": "{fontWeights.headingRegular}",
|
||||||
|
"lineHeight": "{lineHeights.heading}",
|
||||||
|
"fontSize": "{fontSizes.h2}",
|
||||||
|
"paragraphSpacing": "{paragraphSpacing.h2}",
|
||||||
|
"letterSpacing": "{letterSpacing.decreased}"
|
||||||
|
},
|
||||||
|
"$type": "typography"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Body": {
|
||||||
|
"$value": {
|
||||||
|
"fontFamily": "{fontFamilies.body}",
|
||||||
|
"fontWeight": "{fontWeights.bodyRegular}",
|
||||||
|
"lineHeight": "{lineHeights.heading}",
|
||||||
|
"fontSize": "{fontSizes.body}",
|
||||||
|
"paragraphSpacing": "{paragraphSpacing.h2}"
|
||||||
|
},
|
||||||
|
"$type": "typography"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$themes": [],
|
||||||
|
"$metadata": {
|
||||||
|
"tokenSetOrder": ["core", "light", "dark", "theme"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
[app.common.time :as dt]
|
[app.common.time :as dt]
|
||||||
[app.common.transit :as tr]
|
[app.common.transit :as tr]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[clojure.data.json :as json]
|
||||||
[clojure.test :as t]))
|
[clojure.test :as t]))
|
||||||
|
|
||||||
(t/testing "token"
|
(t/testing "token"
|
||||||
|
@ -104,7 +105,25 @@
|
||||||
|
|
||||||
(t/testing "ignore invalid moves"
|
(t/testing "ignore invalid moves"
|
||||||
(t/is (= original-order (move "A" "foo/bar/baz")))
|
(t/is (= original-order (move "A" "foo/bar/baz")))
|
||||||
(t/is (= original-order (move "Missing" "Move")))))))
|
(t/is (= original-order (move "Missing" "Move"))))))
|
||||||
|
|
||||||
|
(t/deftest tokens-tree
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set :name "A"
|
||||||
|
:tokens {"foo.bar.baz" (ctob/make-token :name "foo.bar.baz"
|
||||||
|
:type :boolean
|
||||||
|
:value true)
|
||||||
|
"foo.bar.bam" (ctob/make-token :name "foo.bar.bam"
|
||||||
|
:type :boolean
|
||||||
|
:value true)
|
||||||
|
"baz.boo" (ctob/make-token :name "baz.boo"
|
||||||
|
:type :boolean
|
||||||
|
:value true)})))
|
||||||
|
expected (-> (ctob/get-set tokens-lib "A")
|
||||||
|
(ctob/get-tokens-tree))]
|
||||||
|
(t/is (= (get-in expected ["foo" "bar" "baz" :name]) "foo.bar.baz"))
|
||||||
|
(t/is (= (get-in expected ["foo" "bar" "bam" :name]) "foo.bar.bam"))
|
||||||
|
(t/is (= (get-in expected ["baz" "boo" :name]) "baz.boo")))))
|
||||||
|
|
||||||
(t/testing "token-theme"
|
(t/testing "token-theme"
|
||||||
(t/deftest make-token-theme
|
(t/deftest make-token-theme
|
||||||
|
@ -1026,3 +1045,94 @@
|
||||||
(t/is (= (ctob/theme-count tokens-lib') 1))
|
(t/is (= (ctob/theme-count tokens-lib') 1))
|
||||||
(t/is (= (count themes-tree') 1))
|
(t/is (= (count themes-tree') 1))
|
||||||
(t/is (nil? token-theme'))))))
|
(t/is (nil? token-theme'))))))
|
||||||
|
|
||||||
|
(t/testing "dtcg encoding/decoding"
|
||||||
|
(t/deftest decode-dtcg-json
|
||||||
|
(let [json (-> (slurp "test/common_tests/types/data/tokens-multi-set-example.json")
|
||||||
|
(tr/decode-str))
|
||||||
|
lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json)
|
||||||
|
get-set-token (fn [set-name token-name]
|
||||||
|
(some-> (ctob/get-set lib set-name)
|
||||||
|
(ctob/get-token token-name)
|
||||||
|
(dissoc :modified-at)))]
|
||||||
|
(t/is (= '("core" "light" "dark" "theme") (ctob/get-ordered-set-names lib)))
|
||||||
|
(t/testing "tokens exist in core set"
|
||||||
|
(t/is (= (get-set-token "core" "colors.red.600")
|
||||||
|
{:name "colors.red.600"
|
||||||
|
:type :color
|
||||||
|
:value "#e53e3e"
|
||||||
|
:description nil}))
|
||||||
|
(t/is (= (get-set-token "core" "spacing.multi-value")
|
||||||
|
{:name "spacing.multi-value"
|
||||||
|
:type :spacing
|
||||||
|
:value "{dimension.sm} {dimension.xl}"
|
||||||
|
:description "You can have multiple values in a single spacing token"}))
|
||||||
|
(t/is (= (get-set-token "theme" "button.primary.background")
|
||||||
|
{:name "button.primary.background"
|
||||||
|
:type :color
|
||||||
|
:value "{accent.default}"
|
||||||
|
:description nil})))
|
||||||
|
(t/testing "invalid tokens got discarded"
|
||||||
|
(t/is (nil? (get-set-token "typography" "H1.Bold"))))))
|
||||||
|
|
||||||
|
(t/deftest encode-dtcg-json
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set :name "core"
|
||||||
|
:tokens {"colors.red.600"
|
||||||
|
(ctob/make-token
|
||||||
|
{:name "colors.red.600"
|
||||||
|
:type :color
|
||||||
|
:value "#e53e3e"})
|
||||||
|
"spacing.multi-value"
|
||||||
|
(ctob/make-token
|
||||||
|
{:name "spacing.multi-value"
|
||||||
|
:type :spacing
|
||||||
|
:value "{dimension.sm} {dimension.xl}"
|
||||||
|
:description "You can have multiple values in a single spacing token"})
|
||||||
|
"button.primary.background"
|
||||||
|
(ctob/make-token
|
||||||
|
{:name "button.primary.background"
|
||||||
|
:type :color
|
||||||
|
:value "{accent.default}"})})))
|
||||||
|
expected (ctob/encode-dtcg tokens-lib)]
|
||||||
|
(t/is (= {"core"
|
||||||
|
{"colors" {"red" {"600" {"$value" "#e53e3e"
|
||||||
|
"$type" "color"}}}
|
||||||
|
"spacing"
|
||||||
|
{"multi-value"
|
||||||
|
{"$value" "{dimension.sm} {dimension.xl}"
|
||||||
|
"$type" "spacing"
|
||||||
|
"$description" "You can have multiple values in a single spacing token"}}
|
||||||
|
"button"
|
||||||
|
{"primary" {"background" {"$value" "{accent.default}"
|
||||||
|
"$type" "color"}}}}}
|
||||||
|
expected))))
|
||||||
|
|
||||||
|
(t/deftest encode-decode-dtcg-json
|
||||||
|
(with-redefs [dt/now (constantly #inst "2024-10-16T12:01:20.257840055-00:00")]
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set :name "core"
|
||||||
|
:tokens {"colors.red.600"
|
||||||
|
(ctob/make-token
|
||||||
|
{:name "colors.red.600"
|
||||||
|
:type :color
|
||||||
|
:value "#e53e3e"})
|
||||||
|
"spacing.multi-value"
|
||||||
|
(ctob/make-token
|
||||||
|
{:name "spacing.multi-value"
|
||||||
|
:type :spacing
|
||||||
|
:value "{dimension.sm} {dimension.xl}"
|
||||||
|
:description "You can have multiple values in a single spacing token"})
|
||||||
|
"button.primary.background"
|
||||||
|
(ctob/make-token
|
||||||
|
{:name "button.primary.background"
|
||||||
|
:type :color
|
||||||
|
:value "{accent.default}"})})))
|
||||||
|
encoded (ctob/encode-dtcg tokens-lib)
|
||||||
|
with-prev-tokens-lib (ctob/decode-dtcg-json tokens-lib encoded)
|
||||||
|
with-empty-tokens-lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) encoded)]
|
||||||
|
(t/testing "library got updated but data is equal"
|
||||||
|
(t/is (not= with-prev-tokens-lib tokens-lib))
|
||||||
|
(t/is (= @with-prev-tokens-lib @tokens-lib)))
|
||||||
|
(t/testing "fresh tokens library is also equal"
|
||||||
|
(= @with-empty-tokens-lib @tokens-lib))))))
|
||||||
|
|
|
@ -192,6 +192,24 @@
|
||||||
(dch/commit-changes changes')
|
(dch/commit-changes changes')
|
||||||
(wtu/update-workspace-tokens))))))
|
(wtu/update-workspace-tokens))))))
|
||||||
|
|
||||||
|
(defn import-tokens-lib [lib]
|
||||||
|
(ptk/reify ::import-tokens-lib
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [data (get state :workspace-data)
|
||||||
|
update-token-set-change (some-> lib
|
||||||
|
(ctob/get-sets)
|
||||||
|
(first)
|
||||||
|
(:name)
|
||||||
|
(set-selected-token-set-id))
|
||||||
|
changes (-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-library-data data)
|
||||||
|
(pcb/set-tokens-lib lib))]
|
||||||
|
(rx/of
|
||||||
|
(dch/commit-changes changes)
|
||||||
|
update-token-set-change
|
||||||
|
(wtu/update-workspace-tokens))))))
|
||||||
|
|
||||||
(defn delete-token-set [token-set-name]
|
(defn delete-token-set [token-set-name]
|
||||||
(ptk/reify ::delete-token-set
|
(ptk/reify ::delete-token-set
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
|
@ -284,7 +302,7 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(assoc-in state [:workspace-tokens :open-status token-type] open?))))
|
(assoc-in state [:workspace-tokens :open-status token-type] open?))))
|
||||||
|
|
||||||
;; Token Context Menu Functions -------------------------------------------------
|
;; === Token Context Menu
|
||||||
|
|
||||||
(defn show-token-context-menu
|
(defn show-token-context-menu
|
||||||
[{:keys [position _token-name] :as params}]
|
[{:keys [position _token-name] :as params}]
|
||||||
|
@ -300,6 +318,8 @@
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(assoc-in state [:workspace-local :token-context-menu] nil))))
|
(assoc-in state [:workspace-local :token-context-menu] nil))))
|
||||||
|
|
||||||
|
;; === Token Set Context Menu
|
||||||
|
|
||||||
(defn show-token-set-context-menu
|
(defn show-token-set-context-menu
|
||||||
[{:keys [position _token-set-name] :as params}]
|
[{:keys [position _token-set-name] :as params}]
|
||||||
(dm/assert! (gpt/point? position))
|
(dm/assert! (gpt/point? position))
|
||||||
|
@ -313,3 +333,19 @@
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(assoc-in state [:workspace-local :token-set-context-menu] nil))))
|
(assoc-in state [:workspace-local :token-set-context-menu] nil))))
|
||||||
|
|
||||||
|
;; === Import Export Context Menu
|
||||||
|
|
||||||
|
(defn show-import-export-context-menu
|
||||||
|
[{:keys [position] :as params}]
|
||||||
|
(dm/assert! (gpt/point? position))
|
||||||
|
(ptk/reify ::show-import-export-context-menu
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:workspace-local :import-export-context-menu] params))))
|
||||||
|
|
||||||
|
(def hide-import-export-set-context-menu
|
||||||
|
(ptk/reify ::hide-import-export-set-context-menu
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(assoc-in state [:workspace-local :import-export-set-context-menu] nil))))
|
||||||
|
|
|
@ -7,11 +7,7 @@
|
||||||
(ns app.main.ui.workspace.tokens.core
|
(ns app.main.ui.workspace.tokens.core
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.main.refs :as refs]
|
[app.main.ui.workspace.tokens.token :as wtt]))
|
||||||
[app.main.ui.workspace.tokens.token :as wtt]
|
|
||||||
[app.util.dom :as dom]
|
|
||||||
[app.util.webapi :as wapi]
|
|
||||||
[cuerdas.core :as str]))
|
|
||||||
|
|
||||||
;; Helpers ---------------------------------------------------------------------
|
;; Helpers ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -36,30 +32,3 @@
|
||||||
(cond-> (assoc token :label name)
|
(cond-> (assoc token :label name)
|
||||||
(wtt/token-applied? token shape (or selected-attributes attributes)) (assoc :selected? true)))
|
(wtt/token-applied? token shape (or selected-attributes attributes)) (assoc :selected? true)))
|
||||||
tokens))
|
tokens))
|
||||||
|
|
||||||
;; JSON export functions -------------------------------------------------------
|
|
||||||
|
|
||||||
(defn encode-tokens
|
|
||||||
[data]
|
|
||||||
(-> data
|
|
||||||
(clj->js)
|
|
||||||
(js/JSON.stringify nil 2)))
|
|
||||||
|
|
||||||
(defn export-tokens-file [tokens-json]
|
|
||||||
(let [file-name "tokens.json"
|
|
||||||
file-content (encode-tokens tokens-json)
|
|
||||||
blob (wapi/create-blob file-content "application/json")]
|
|
||||||
(dom/trigger-download file-name blob)))
|
|
||||||
|
|
||||||
(defn tokens->dtcg-map [tokens]
|
|
||||||
(let [global (reduce
|
|
||||||
(fn [acc [_ {:keys [name value type]}]]
|
|
||||||
(assoc acc name {"$value" value
|
|
||||||
"$type" (str/camel type)}))
|
|
||||||
(d/ordered-map) tokens)]
|
|
||||||
{:global global}))
|
|
||||||
|
|
||||||
(defn download-tokens-as-json []
|
|
||||||
(let [tokens (deref refs/workspace-active-theme-sets-tokens)
|
|
||||||
dtcg-format-tokens-map (tokens->dtcg-map tokens)]
|
|
||||||
(export-tokens-file dtcg-format-tokens-map)))
|
|
||||||
|
|
|
@ -1,580 +0,0 @@
|
||||||
{
|
|
||||||
"core": {
|
|
||||||
"dimension": {
|
|
||||||
"scale": {
|
|
||||||
"value": "2",
|
|
||||||
"type": "dimension"
|
|
||||||
},
|
|
||||||
"xs": {
|
|
||||||
"value": "4",
|
|
||||||
"type": "dimension"
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"value": "{dimension.xs} * {dimension.scale}",
|
|
||||||
"type": "dimension"
|
|
||||||
},
|
|
||||||
"md": {
|
|
||||||
"value": "{dimension.sm} * {dimension.scale}",
|
|
||||||
"type": "dimension"
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"value": "{dimension.md} * {dimension.scale}",
|
|
||||||
"type": "dimension"
|
|
||||||
},
|
|
||||||
"xl": {
|
|
||||||
"value": "{dimension.lg} * {dimension.scale}",
|
|
||||||
"type": "dimension"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spacing": {
|
|
||||||
"xs": {
|
|
||||||
"value": "{dimension.xs}",
|
|
||||||
"type": "spacing"
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"value": "{dimension.sm}",
|
|
||||||
"type": "spacing"
|
|
||||||
},
|
|
||||||
"md": {
|
|
||||||
"value": "{dimension.md}",
|
|
||||||
"type": "spacing"
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"value": "{dimension.lg}",
|
|
||||||
"type": "spacing"
|
|
||||||
},
|
|
||||||
"xl": {
|
|
||||||
"value": "{dimension.xl}",
|
|
||||||
"type": "spacing"
|
|
||||||
},
|
|
||||||
"multi-value": {
|
|
||||||
"value": "{dimension.sm} {dimension.xl}",
|
|
||||||
"type": "spacing",
|
|
||||||
"description": "You can have multiple values in a single spacing token. Read more on these: https://docs.tokens.studio/available-tokens/spacing-tokens#multi-value-spacing-tokens"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"borderRadius": {
|
|
||||||
"sm": {
|
|
||||||
"value": "4",
|
|
||||||
"type": "borderRadius"
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"value": "8",
|
|
||||||
"type": "borderRadius"
|
|
||||||
},
|
|
||||||
"xl": {
|
|
||||||
"value": "16",
|
|
||||||
"type": "borderRadius"
|
|
||||||
},
|
|
||||||
"multi-value": {
|
|
||||||
"value": "{borderRadius.sm} {borderRadius.lg}",
|
|
||||||
"type": "borderRadius",
|
|
||||||
"description": "You can have multiple values in a single radius token. Read more on these: https://docs.tokens.studio/available-tokens/border-radius-tokens#single--multiple-values"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"colors": {
|
|
||||||
"black": {
|
|
||||||
"value": "#000000",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"white": {
|
|
||||||
"value": "#ffffff",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"gray": {
|
|
||||||
"100": {
|
|
||||||
"value": "#f7fafc",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#edf2f7",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#e2e8f0",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#cbd5e0",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#a0aec0",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#718096",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#4a5568",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#2d3748",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#1a202c",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"red": {
|
|
||||||
"100": {
|
|
||||||
"value": "#fff5f5",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#fed7d7",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#feb2b2",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#fc8181",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#f56565",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#e53e3e",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#c53030",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#9b2c2c",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#742a2a",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"orange": {
|
|
||||||
"100": {
|
|
||||||
"value": "#fffaf0",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#feebc8",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#fbd38d",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#f6ad55",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#ed8936",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#dd6b20",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#c05621",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#9c4221",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#7b341e",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yellow": {
|
|
||||||
"100": {
|
|
||||||
"value": "#fffff0",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#fefcbf",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#faf089",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#f6e05e",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#ecc94b",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#d69e2e",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#b7791f",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#975a16",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#744210",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"green": {
|
|
||||||
"100": {
|
|
||||||
"value": "#f0fff4",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#c6f6d5",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#9ae6b4",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#68d391",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#48bb78",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#38a169",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#2f855a",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#276749",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#22543d",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"teal": {
|
|
||||||
"100": {
|
|
||||||
"value": "#e6fffa",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#b2f5ea",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#81e6d9",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#4fd1c5",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#38b2ac",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#319795",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#2c7a7b",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#285e61",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#234e52",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"blue": {
|
|
||||||
"100": {
|
|
||||||
"value": "#ebf8ff",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#bee3f8",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#90cdf4",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#63b3ed",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#4299e1",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#3182ce",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#2b6cb0",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#2c5282",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#2a4365",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indigo": {
|
|
||||||
"100": {
|
|
||||||
"value": "#ebf4ff",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#c3dafe",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#a3bffa",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#7f9cf5",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#667eea",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#5a67d8",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#4c51bf",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#434190",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#3c366b",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"purple": {
|
|
||||||
"100": {
|
|
||||||
"value": "#faf5ff",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#e9d8fd",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#d6bcfa",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#b794f4",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#9f7aea",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#805ad5",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#6b46c1",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#553c9a",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#44337a",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pink": {
|
|
||||||
"100": {
|
|
||||||
"value": "#fff5f7",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"200": {
|
|
||||||
"value": "#fed7e2",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"300": {
|
|
||||||
"value": "#fbb6ce",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"value": "#f687b3",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"value": "#ed64a6",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"600": {
|
|
||||||
"value": "#d53f8c",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"700": {
|
|
||||||
"value": "#b83280",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"800": {
|
|
||||||
"value": "#97266d",
|
|
||||||
"type": "color"
|
|
||||||
},
|
|
||||||
"900": {
|
|
||||||
"value": "#702459",
|
|
||||||
"type": "color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"opacity": {
|
|
||||||
"low": {
|
|
||||||
"value": "10%",
|
|
||||||
"type": "opacity"
|
|
||||||
},
|
|
||||||
"md": {
|
|
||||||
"value": "50%",
|
|
||||||
"type": "opacity"
|
|
||||||
},
|
|
||||||
"high": {
|
|
||||||
"value": "90%",
|
|
||||||
"type": "opacity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fontFamilies": {
|
|
||||||
"heading": {
|
|
||||||
"value": "Inter",
|
|
||||||
"type": "fontFamilies"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"value": "Roboto",
|
|
||||||
"type": "fontFamilies"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lineHeights": {
|
|
||||||
"heading": {
|
|
||||||
"value": "110%",
|
|
||||||
"type": "lineHeights"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"value": "140%",
|
|
||||||
"type": "lineHeights"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"letterSpacing": {
|
|
||||||
"default": {
|
|
||||||
"value": "0",
|
|
||||||
"type": "letterSpacing"
|
|
||||||
},
|
|
||||||
"increased": {
|
|
||||||
"value": "150%",
|
|
||||||
"type": "letterSpacing"
|
|
||||||
},
|
|
||||||
"decreased": {
|
|
||||||
"value": "-5%",
|
|
||||||
"type": "letterSpacing"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"paragraphSpacing": {
|
|
||||||
"h1": {
|
|
||||||
"value": "32",
|
|
||||||
"type": "paragraphSpacing"
|
|
||||||
},
|
|
||||||
"h2": {
|
|
||||||
"value": "26",
|
|
||||||
"type": "paragraphSpacing"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fontWeights": {
|
|
||||||
"headingRegular": {
|
|
||||||
"value": "Regular",
|
|
||||||
"type": "fontWeights"
|
|
||||||
},
|
|
||||||
"headingBold": {
|
|
||||||
"value": "Bold",
|
|
||||||
"type": "fontWeights"
|
|
||||||
},
|
|
||||||
"bodyRegular": {
|
|
||||||
"value": "Regular",
|
|
||||||
"type": "fontWeights"
|
|
||||||
},
|
|
||||||
"bodyBold": {
|
|
||||||
"value": "Bold",
|
|
||||||
"type": "fontWeights"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fontSizes": {
|
|
||||||
"h1": {
|
|
||||||
"value": "{fontSizes.h2} * 1.25",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"h2": {
|
|
||||||
"value": "{fontSizes.h3} * 1.25",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"h3": {
|
|
||||||
"value": "{fontSizes.h4} * 1.25",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"h4": {
|
|
||||||
"value": "{fontSizes.h5} * 1.25",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"h5": {
|
|
||||||
"value": "{fontSizes.h6} * 1.25",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"h6": {
|
|
||||||
"value": "{fontSizes.body} * 1",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"value": "16",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"value": "{fontSizes.body} * 0.85",
|
|
||||||
"type": "fontSizes"
|
|
||||||
},
|
|
||||||
"xs": {
|
|
||||||
"value": "{fontSizes.body} * 0.65",
|
|
||||||
"type": "fontSizes"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -205,7 +205,7 @@ Token names should only contain letters and digits separated by . characters.")}
|
||||||
selected-set-tokens-tree (mf/use-memo
|
selected-set-tokens-tree (mf/use-memo
|
||||||
(mf/deps token-path selected-set-tokens)
|
(mf/deps token-path selected-set-tokens)
|
||||||
(fn []
|
(fn []
|
||||||
(-> (wtt/token-names-tree selected-set-tokens)
|
(-> (ctob/tokens-tree selected-set-tokens)
|
||||||
;; Allow setting editing token to it's own path
|
;; Allow setting editing token to it's own path
|
||||||
(d/dissoc-in token-path))))
|
(d/dissoc-in token-path))))
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,16 @@
|
||||||
(:require-macros [app.main.style :as stl])
|
(:require-macros [app.main.style :as stl])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.transit :as t]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[app.main.data.messages :as msg]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.tokens :as dt]
|
[app.main.data.tokens :as dt]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.color-bullet :refer [color-bullet]]
|
[app.main.ui.components.color-bullet :refer [color-bullet]]
|
||||||
|
[app.main.ui.components.dropdown-menu :refer [dropdown-menu
|
||||||
|
dropdown-menu-item*]]
|
||||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||||
[app.main.ui.hooks :as h]
|
[app.main.ui.hooks :as h]
|
||||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||||
|
@ -21,7 +25,6 @@
|
||||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||||
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
|
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
|
||||||
[app.main.ui.workspace.tokens.core :as wtc]
|
|
||||||
[app.main.ui.workspace.tokens.sets :refer [sets-list]]
|
[app.main.ui.workspace.tokens.sets :refer [sets-list]]
|
||||||
[app.main.ui.workspace.tokens.sets-context :as sets-context]
|
[app.main.ui.workspace.tokens.sets-context :as sets-context]
|
||||||
[app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu]]
|
[app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu]]
|
||||||
|
@ -30,7 +33,8 @@
|
||||||
[app.main.ui.workspace.tokens.token :as wtt]
|
[app.main.ui.workspace.tokens.token :as wtt]
|
||||||
[app.main.ui.workspace.tokens.token-types :as wtty]
|
[app.main.ui.workspace.tokens.token-types :as wtty]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.storage :refer [storage]]
|
[app.util.webapi :as wapi]
|
||||||
|
[beicon.v2.core :as rx]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.v2 :as mf]
|
[rumext.v2 :as mf]
|
||||||
|
@ -140,7 +144,7 @@
|
||||||
(when open?
|
(when open?
|
||||||
[:& cmm/asset-section-block {:role :content}
|
[:& cmm/asset-section-block {:role :content}
|
||||||
[:div {:class (stl/css :token-pills-wrapper)}
|
[:div {:class (stl/css :token-pills-wrapper)}
|
||||||
(for [token (sort-by :modified-at tokens)]
|
(for [token (sort-by :name tokens)]
|
||||||
(let [theme-token (get active-theme-tokens (wtt/token-identifier token))]
|
(let [theme-token (get active-theme-tokens (wtt/token-identifier token))]
|
||||||
[:& token-pill
|
[:& token-pill
|
||||||
{:key (:name token)
|
{:key (:name token)
|
||||||
|
@ -173,10 +177,10 @@
|
||||||
(modal/show! :tokens/themes {}))}
|
(modal/show! :tokens/themes {}))}
|
||||||
(if create? "Create" "Edit")])
|
(if create? "Create" "Edit")])
|
||||||
|
|
||||||
(mf/defc themes-sidebar
|
(mf/defc themes-header
|
||||||
[_props]
|
[_props]
|
||||||
(let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)]
|
(let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)]
|
||||||
[:div {:class (stl/css :theme-sidebar)}
|
[:div {:class (stl/css :themes-wrapper)}
|
||||||
[:span {:class (stl/css :themes-header)} "Themes"]
|
[:span {:class (stl/css :themes-header)} "Themes"]
|
||||||
[:div {:class (stl/css :theme-select-wrapper)}
|
[:div {:class (stl/css :theme-select-wrapper)}
|
||||||
[:& theme-select]
|
[:& theme-select]
|
||||||
|
@ -191,13 +195,14 @@
|
||||||
(on-create))}
|
(on-create))}
|
||||||
i/add]))
|
i/add]))
|
||||||
|
|
||||||
(mf/defc sets-sidebar
|
(mf/defc themes-sets-tab
|
||||||
[]
|
[]
|
||||||
(let [open? (mf/use-state true)
|
(let [open? (mf/use-state true)
|
||||||
on-open (mf/use-fn #(reset! open? true))]
|
on-open (mf/use-fn #(reset! open? true))]
|
||||||
[:& sets-context/provider {}
|
[:& sets-context/provider {}
|
||||||
[:& sets-context-menu]
|
[:& sets-context-menu]
|
||||||
[:div {:class (stl/css :sets-sidebar)}
|
[:div {:class (stl/css :sets-sidebar)}
|
||||||
|
[:& themes-header]
|
||||||
[:div {:class (stl/css :sidebar-header)}
|
[:div {:class (stl/css :sidebar-header)}
|
||||||
[:& title-bar {:collapsable true
|
[:& title-bar {:collapsable true
|
||||||
:collapsed (not @open?)
|
:collapsed (not @open?)
|
||||||
|
@ -209,10 +214,9 @@
|
||||||
[:& h/sortable-container {}
|
[:& h/sortable-container {}
|
||||||
[:& sets-list]])]]))
|
[:& sets-list]])]]))
|
||||||
|
|
||||||
(mf/defc tokens-explorer
|
(mf/defc tokens-tab
|
||||||
[_props]
|
[_props]
|
||||||
(let [open? (mf/use-state true)
|
(let [objects (mf/deref refs/workspace-page-objects)
|
||||||
objects (mf/deref refs/workspace-page-objects)
|
|
||||||
|
|
||||||
selected (mf/deref refs/selected-shapes)
|
selected (mf/deref refs/selected-shapes)
|
||||||
selected-shapes (into [] (keep (d/getf objects)) selected)
|
selected-shapes (into [] (keep (d/getf objects)) selected)
|
||||||
|
@ -222,70 +226,125 @@
|
||||||
tokens (sd/use-resolved-workspace-tokens)
|
tokens (sd/use-resolved-workspace-tokens)
|
||||||
token-groups (mf/with-memo [tokens]
|
token-groups (mf/with-memo [tokens]
|
||||||
(sorted-token-groups tokens))]
|
(sorted-token-groups tokens))]
|
||||||
[:article
|
[:*
|
||||||
[:& token-context-menu]
|
[:& token-context-menu]
|
||||||
[:& title-bar {:collapsable true
|
[:& title-bar {:all-clickable true
|
||||||
:collapsed (not @open?)
|
:title "TOKENS"}]
|
||||||
:all-clickable true
|
[:div.assets-bar
|
||||||
:title "TOKENS"
|
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups)
|
||||||
:on-collapsed #(swap! open? not)}]
|
(:empty token-groups))]
|
||||||
(when @open?
|
[:& token-component {:key token-key
|
||||||
[:div.assets-bar
|
:type token-key
|
||||||
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups)
|
:selected-shapes selected-shapes
|
||||||
(:empty token-groups))]
|
:active-theme-tokens active-theme-tokens
|
||||||
[:& token-component {:key token-key
|
:tokens tokens
|
||||||
:type token-key
|
:token-type-props token-type-props}])]]))
|
||||||
:selected-shapes selected-shapes
|
|
||||||
:active-theme-tokens active-theme-tokens
|
|
||||||
:tokens tokens
|
|
||||||
:token-type-props token-type-props}])])]))
|
|
||||||
|
|
||||||
(defn dev-or-preview-url? [url]
|
(mf/defc json-import-button []
|
||||||
(let [host (-> url js/URL. .-host)
|
(let []
|
||||||
localhost? (= "localhost" (first (str/split host #":")))
|
[:div
|
||||||
pr? (str/ends-with? host "penpot.alpha.tokens.studio")]
|
|
||||||
(or localhost? pr?)))
|
|
||||||
|
|
||||||
(defn location-url-dev-or-preview-url!? []
|
[:button {:class (stl/css :download-json-button)
|
||||||
(dev-or-preview-url? js/window.location.href))
|
:on-click #(.click (js/document.getElementById "file-input"))}
|
||||||
|
download-icon
|
||||||
|
"Import JSON"]]))
|
||||||
|
|
||||||
(defn temp-use-themes-flag []
|
(mf/defc import-export-button
|
||||||
(let [show? (mf/use-state (or
|
{::mf/wrap-props false}
|
||||||
(location-url-dev-or-preview-url!?)
|
[{:keys []}]
|
||||||
(get @storage ::show-token-themes-sets?)
|
(let [show-menu* (mf/use-state false)
|
||||||
true))]
|
show-menu? (deref show-menu*)
|
||||||
(mf/use-effect
|
|
||||||
(fn []
|
open-menu
|
||||||
(letfn [(toggle! []
|
(mf/use-fn
|
||||||
(swap! storage update ::show-token-themes-sets? not)
|
(fn [event]
|
||||||
(reset! show? (get @storage ::show-token-themes-sets?)))]
|
(dom/stop-propagation event)
|
||||||
(set! js/window.toggleThemes toggle!))))
|
(reset! show-menu* true)))
|
||||||
show?))
|
|
||||||
|
close-menu
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(reset! show-menu* false)))
|
||||||
|
|
||||||
|
input-ref (mf/use-ref)
|
||||||
|
on-import
|
||||||
|
(fn [event]
|
||||||
|
(let [file (-> event .-target .-files (aget 0))]
|
||||||
|
(->> (wapi/read-file-as-text file)
|
||||||
|
(rx/map (fn [data]
|
||||||
|
(try
|
||||||
|
(t/decode-str data)
|
||||||
|
(catch js/Error e
|
||||||
|
(throw (ex-info "Json parse error"
|
||||||
|
{:user-error "Import Error: Could not parse json"
|
||||||
|
:type :json-parse-error
|
||||||
|
:data data
|
||||||
|
:exception e}))))))
|
||||||
|
(rx/map (fn [json-data]
|
||||||
|
(try
|
||||||
|
(ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json-data)
|
||||||
|
(catch js/Error e
|
||||||
|
(throw (ex-info "invalid token data"
|
||||||
|
{:user-error "Import Error: Invalid token data in json."
|
||||||
|
:type :invalid-token-data
|
||||||
|
:data json-data
|
||||||
|
:exception e}))))))
|
||||||
|
(rx/subs! (fn [lib]
|
||||||
|
(st/emit! (dt/import-tokens-lib lib)))
|
||||||
|
(fn [err]
|
||||||
|
(let [{:keys [user-error]} (ex-data err)]
|
||||||
|
(st/emit! (msg/show {:content user-error
|
||||||
|
:notification-type :toast
|
||||||
|
:type :warning
|
||||||
|
:timeout 3000}))))))
|
||||||
|
(set! (.-value (mf/ref-val input-ref)) "")))
|
||||||
|
on-export (fn []
|
||||||
|
(let [tokens-blob (some-> (deref refs/tokens-lib)
|
||||||
|
(ctob/encode-dtcg)
|
||||||
|
(clj->js)
|
||||||
|
(js/JSON.stringify nil 2)
|
||||||
|
(wapi/create-blob "application/json"))]
|
||||||
|
(dom/trigger-download "tokens.json" tokens-blob)))]
|
||||||
|
[:div {:class (stl/css :import-export-button-wrapper)}
|
||||||
|
[:input {:type "file"
|
||||||
|
:ref input-ref
|
||||||
|
:style {:display "none"}
|
||||||
|
:id "file-input"
|
||||||
|
:accept ".json"
|
||||||
|
:on-change on-import}]
|
||||||
|
[:button {:class (stl/css :import-export-button)
|
||||||
|
:on-click open-menu}
|
||||||
|
download-icon
|
||||||
|
"Tokens"]
|
||||||
|
[:& dropdown-menu {:show show-menu?
|
||||||
|
:on-close close-menu
|
||||||
|
:list-class (stl/css :import-export-menu)}
|
||||||
|
[:> dropdown-menu-item* {:class (stl/css :import-export-menu-item)
|
||||||
|
:on-click #(.click (mf/ref-val input-ref))}
|
||||||
|
"Import"]
|
||||||
|
|
||||||
|
[:> dropdown-menu-item* {:class (stl/css :import-export-menu-item)
|
||||||
|
:on-click on-export}
|
||||||
|
"Export"]]]))
|
||||||
|
|
||||||
(mf/defc tokens-sidebar-tab
|
(mf/defc tokens-sidebar-tab
|
||||||
{::mf/wrap [mf/memo]
|
{::mf/wrap [mf/memo]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
[_props]
|
[_props]
|
||||||
(let [show-sets-section? (deref (temp-use-themes-flag))
|
(let [{on-pointer-down-pages :on-pointer-down
|
||||||
{on-pointer-down-pages :on-pointer-down
|
|
||||||
on-lost-pointer-capture-pages :on-lost-pointer-capture
|
on-lost-pointer-capture-pages :on-lost-pointer-capture
|
||||||
on-pointer-move-pages :on-pointer-move
|
on-pointer-move-pages :on-pointer-move
|
||||||
size-pages-opened :size}
|
size-pages-opened :size}
|
||||||
(use-resize-hook :sitemap 200 38 400 :y false nil)]
|
(use-resize-hook :tokens 200 38 400 :y false nil)]
|
||||||
[:div {:class (stl/css :sidebar-tab-wrapper)}
|
[:div {:class (stl/css :sidebar-wrapper)}
|
||||||
(when show-sets-section?
|
[:article {:class (stl/css :sets-section-wrapper)
|
||||||
[:div {:class (stl/css :sets-section-wrapper)
|
:style {"--resize-height" (str size-pages-opened "px")}}
|
||||||
:style {:height (str size-pages-opened "px")}}
|
[:& themes-sets-tab]]
|
||||||
[:& themes-sidebar]
|
[:article {:class (stl/css :tokens-section-wrapper)}
|
||||||
[:& sets-sidebar]])
|
[:div {:class (stl/css :resize-area-horiz)
|
||||||
[:div {:class (stl/css :tokens-section-wrapper)}
|
:on-pointer-down on-pointer-down-pages
|
||||||
(when show-sets-section?
|
:on-lost-pointer-capture on-lost-pointer-capture-pages
|
||||||
[:div {:class (stl/css :resize-area-horiz)
|
:on-pointer-move on-pointer-move-pages}]
|
||||||
:on-pointer-down on-pointer-down-pages
|
[:& tokens-tab]
|
||||||
:on-lost-pointer-capture on-lost-pointer-capture-pages
|
[:& import-export-button]]]))
|
||||||
:on-pointer-move on-pointer-move-pages}])
|
|
||||||
[:& tokens-explorer]]
|
|
||||||
[:button {:class (stl/css :download-json-button)
|
|
||||||
:on-click wtc/download-tokens-as-json}
|
|
||||||
download-icon
|
|
||||||
"Export JSON"]]))
|
|
||||||
|
|
|
@ -7,25 +7,37 @@
|
||||||
@import "refactor/common-refactor.scss";
|
@import "refactor/common-refactor.scss";
|
||||||
@import "./common.scss";
|
@import "./common.scss";
|
||||||
|
|
||||||
.sidebar-tab-wrapper {
|
.sidebar-wrapper {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: auto auto 1fr;
|
||||||
height: 100%;
|
// Overflow on the bottom section can't be done without hardcoded values for the height
|
||||||
|
// This has to be changed from the wrapping sidebar styles
|
||||||
|
height: calc(100vh - #{$s-84});
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sets-section-wrapper {
|
.sets-section-wrapper {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
height: var(--resize-height);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: $s-8;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tokens-section-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
padding-left: $s-12;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sets-sidebar {
|
.sets-sidebar {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-sidebar {
|
.themes-wrapper {
|
||||||
padding: $s-12;
|
padding: $s-12;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
@ -52,18 +64,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tokens-section-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
padding-top: $s-12;
|
|
||||||
padding-left: $s-12;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Remove once sets are available to public
|
|
||||||
.sets-section-wrapper + .tokens-section-wrapper {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-pills-wrapper {
|
.token-pills-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $s-4;
|
gap: $s-4;
|
||||||
|
@ -103,11 +103,14 @@
|
||||||
translate: 0px -1px;
|
translate: 0px -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-json-button {
|
.import-export-button-wrapper {
|
||||||
@extend .button-secondary;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: $s-12;
|
bottom: $s-12;
|
||||||
right: $s-12;
|
right: $s-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-export-button {
|
||||||
|
@extend .button-secondary;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: $s-6 $s-8;
|
padding: $s-6 $s-8;
|
||||||
|
@ -122,6 +125,38 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.import-export-menu {
|
||||||
|
@extend .menu-dropdown;
|
||||||
|
top: -#{$s-6};
|
||||||
|
right: 0;
|
||||||
|
translate: 0 -100%;
|
||||||
|
width: $s-192;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-export-menu-item {
|
||||||
|
@extend .menu-item-base;
|
||||||
|
cursor: pointer;
|
||||||
|
.open-arrow {
|
||||||
|
@include flexCenter;
|
||||||
|
svg {
|
||||||
|
@extend .button-icon;
|
||||||
|
stroke: var(--icon-foreground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: var(--menu-foreground-color-hover);
|
||||||
|
.open-arrow {
|
||||||
|
svg {
|
||||||
|
stroke: var(--menu-foreground-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.shortcut-key {
|
||||||
|
color: var(--menu-shortcut-foreground-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.theme-select-wrapper {
|
.theme-select-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 0.28fr;
|
grid-template-columns: 1fr 0.28fr;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
(ns app.main.ui.workspace.tokens.token
|
(ns app.main.ui.workspace.tokens.token
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
|
||||||
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
|
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
@ -22,13 +21,6 @@
|
||||||
{:value parsed-value
|
{:value parsed-value
|
||||||
:unit unit}))))
|
:unit unit}))))
|
||||||
|
|
||||||
(defn find-token-references
|
|
||||||
"Finds token reference values in `value-string` and returns a set with all contained namespaces."
|
|
||||||
[value-string]
|
|
||||||
(some->> (re-seq #"\{([^}]*)\}" value-string)
|
|
||||||
(map second)
|
|
||||||
(into #{})))
|
|
||||||
|
|
||||||
(defn token-identifier [{:keys [name] :as _token}]
|
(defn token-identifier [{:keys [name] :as _token}]
|
||||||
name)
|
name)
|
||||||
|
|
||||||
|
@ -96,14 +88,6 @@
|
||||||
{:path (seq path)
|
{:path (seq path)
|
||||||
:selector selector}))
|
:selector selector}))
|
||||||
|
|
||||||
(defn token-names-map
|
|
||||||
"Convert tokens into a map with their `:name` as the key.
|
|
||||||
|
|
||||||
E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}"
|
|
||||||
[tokens]
|
|
||||||
(->> (map (fn [{:keys [name] :as token}] [name token]) tokens)
|
|
||||||
(into {})))
|
|
||||||
|
|
||||||
(defn token-names-tree-id-map [tokens]
|
(defn token-names-tree-id-map [tokens]
|
||||||
(reduce
|
(reduce
|
||||||
(fn [acc [_ {:keys [name] :as token}]]
|
(fn [acc [_ {:keys [name] :as token}]]
|
||||||
|
@ -117,16 +101,6 @@
|
||||||
:ids-map {}}
|
:ids-map {}}
|
||||||
tokens))
|
tokens))
|
||||||
|
|
||||||
(defn token-names-tree
|
|
||||||
"Convert tokens into a nested tree with their `:name` as the path."
|
|
||||||
[tokens]
|
|
||||||
(reduce
|
|
||||||
(fn [acc [_ {:keys [name] :as token}]]
|
|
||||||
(when (string? name)
|
|
||||||
(let [path (token-name->path name)]
|
|
||||||
(assoc-in acc path token))))
|
|
||||||
{} tokens))
|
|
||||||
|
|
||||||
(defn token-name-path-exists?
|
(defn token-name-path-exists?
|
||||||
"Traverses the path from `token-name` down a `token-tree` and checks if a token at that path exists.
|
"Traverses the path from `token-name` down a `token-tree` and checks if a token at that path exists.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
[app.common.test-helpers.shapes :as cths]
|
[app.common.test-helpers.shapes :as cths]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||||
|
[app.main.ui.workspace.tokens.token :as wtt]
|
||||||
[cljs.test :as t :include-macros true]
|
[cljs.test :as t :include-macros true]
|
||||||
[frontend-tests.helpers.pages :as thp]
|
[frontend-tests.helpers.pages :as thp]
|
||||||
[frontend-tests.helpers.state :as ths]
|
[frontend-tests.helpers.state :as ths]
|
||||||
|
@ -133,34 +134,6 @@
|
||||||
(t/testing "while :r4 was kept with borderRadius.sm"
|
(t/testing "while :r4 was kept with borderRadius.sm"
|
||||||
(t/is (= (:r4 (:applied-tokens rect-1')) (:name token-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 [file (-> (setup-file-with-tokens)
|
|
||||||
(toht/add-token :token-target {:value "100"
|
|
||||||
:name "dimensions.sm"
|
|
||||||
:type :dimensions}))
|
|
||||||
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 :token-target)
|
|
||||||
: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' :token-target)
|
|
||||||
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')) (wtt/token-identifier token-target')))
|
|
||||||
(t/is (= (:height (:applied-tokens rect-1')) (wtt/token-identifier 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-dimensions
|
(t/deftest test-apply-dimensions
|
||||||
(t/testing "applies dimensions token and updates the shapes width and height"
|
(t/testing "applies dimensions token and updates the shapes width and height"
|
||||||
(t/async
|
(t/async
|
||||||
|
|
|
@ -39,22 +39,3 @@
|
||||||
(t/is (= 24 (get-in resolved-tokens ["borderRadius.md-with-dashes" :resolved-value])))
|
(t/is (= 24 (get-in resolved-tokens ["borderRadius.md-with-dashes" :resolved-value])))
|
||||||
(t/is (= "px" (get-in resolved-tokens ["borderRadius.md-with-dashes" :unit])))
|
(t/is (= "px" (get-in resolved-tokens ["borderRadius.md-with-dashes" :unit])))
|
||||||
(done))))))))
|
(done))))))))
|
||||||
|
|
||||||
(t/deftest resolve-tokens-names-map-test
|
|
||||||
(t/async
|
|
||||||
done
|
|
||||||
(t/testing "resolves tokens using style-dictionary from a names map"
|
|
||||||
(-> (vals tokens)
|
|
||||||
(wtt/token-names-map)
|
|
||||||
(sd/resolve-tokens+ {:names-map? true})
|
|
||||||
(p/finally (fn [resolved-tokens]
|
|
||||||
(let [expected-tokens {"borderRadius.sm"
|
|
||||||
(assoc border-radius-token
|
|
||||||
:resolved-value 12
|
|
||||||
:unit "px")
|
|
||||||
"borderRadius.md-with-dashes"
|
|
||||||
(assoc reference-border-radius-token
|
|
||||||
:resolved-value 24
|
|
||||||
:unit "px")}]
|
|
||||||
(t/is (= expected-tokens resolved-tokens))
|
|
||||||
(done))))))))
|
|
||||||
|
|
|
@ -91,26 +91,6 @@
|
||||||
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz")))
|
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz")))
|
||||||
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz...."))))
|
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz...."))))
|
||||||
|
|
||||||
(t/deftest tokens-name-map-test
|
|
||||||
(t/testing "creates a a names map from tokens"
|
|
||||||
(t/is (= {"border-radius.sm" {:name "border-radius.sm", :value "10"}
|
|
||||||
"border-radius.md" {:name "border-radius.md", :value "20"}}
|
|
||||||
(wtt/token-names-map [{:name "border-radius.sm" :value "10"}
|
|
||||||
{:name "border-radius.md" :value "20"}])))))
|
|
||||||
|
|
||||||
(t/deftest tokens-name-tree-test
|
|
||||||
(t/is (= {"foo"
|
|
||||||
{"bar"
|
|
||||||
{"baz" {:name "foo.bar.baz", :value "a"},
|
|
||||||
"bam" {:name "foo.bar.bam", :value "b"}}},
|
|
||||||
"baz" {"bar" {"foo" {:name "baz.bar.foo", :value "{foo.bar.baz}"}}}}
|
|
||||||
(wtt/token-names-tree {:a {:name "foo.bar.baz"
|
|
||||||
:value "a"}
|
|
||||||
:b {:name "foo.bar.bam"
|
|
||||||
:value "b"}
|
|
||||||
:c {:name "baz.bar.foo"
|
|
||||||
:value "{foo.bar.baz}"}}))))
|
|
||||||
|
|
||||||
(t/deftest token-name-path-exists?-test
|
(t/deftest token-name-path-exists?-test
|
||||||
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {"sm" {:name "sm"}}})))
|
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {"sm" {:name "sm"}}})))
|
||||||
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {:name "sm"}})))
|
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {:name "sm"}})))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue