mirror of
https://github.com/penpot/penpot.git
synced 2025-08-01 04:28:22 +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-theme :as ctot]
|
||||
[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.typography :as ctt]
|
||||
[clojure.set :as set]))
|
||||
|
@ -306,6 +304,11 @@
|
|||
[:type [:= :del-token-set]]
|
||||
[:name :string]]]
|
||||
|
||||
[:set-tokens-lib
|
||||
[:map {:title "SetTokensLib"}
|
||||
[:type [:= :set-tokens-lib]]
|
||||
[:tokens-lib :any]]]
|
||||
|
||||
[:add-token
|
||||
[:map {:title "AddTokenChange"}
|
||||
[:type [:= :add-token]]
|
||||
|
@ -792,6 +795,10 @@
|
|||
|
||||
;; -- Tokens
|
||||
|
||||
(defmethod process-change :set-tokens-lib
|
||||
[data {:keys [tokens-lib]}]
|
||||
(assoc data :tokens-lib tokens-lib))
|
||||
|
||||
(defmethod process-change :add-token
|
||||
[data {:keys [set-name token]}]
|
||||
(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})
|
||||
(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
|
||||
[changes set-name token]
|
||||
(-> changes
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.schema.registry :as sr]
|
||||
[clojure.set :as set]
|
||||
[malli.util :as mu]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -32,21 +33,26 @@
|
|||
;; 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
|
||||
#{:boolean
|
||||
:border-radius
|
||||
:box-shadow
|
||||
:color
|
||||
:dimensions
|
||||
:numeric
|
||||
:opacity
|
||||
:other
|
||||
:rotation
|
||||
:sizing
|
||||
:spacing
|
||||
:string
|
||||
:stroke-width
|
||||
:typography})
|
||||
(into #{} (keys token-type->dtcg-token-type)))
|
||||
|
||||
(defn valid-token-type?
|
||||
[t]
|
||||
|
|
|
@ -99,6 +99,14 @@
|
|||
|
||||
;; === 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])
|
||||
|
||||
(def schema:token
|
||||
|
@ -178,12 +186,25 @@
|
|||
(defn split-token-set-path [path]
|
||||
(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
|
||||
(add-token [_ token] "add a token at the end of the list")
|
||||
(update-token [_ token-name f] "update a token in the list")
|
||||
(delete-token [_ token-name] "delete a token from the list")
|
||||
(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]
|
||||
ITokenSet
|
||||
|
@ -219,7 +240,16 @@
|
|||
(get tokens token-name))
|
||||
|
||||
(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
|
||||
[:and [:map {:title "TokenSet"}
|
||||
|
@ -440,6 +470,31 @@ When `before-set-name` is nil, move set to bottom")
|
|||
(def valid-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
|
||||
|
||||
(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-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")
|
||||
(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 [_]))
|
||||
|
||||
(deftype TokensLib [sets set-groups themes active-themes]
|
||||
|
@ -724,6 +781,25 @@ When `before-set-name` is nil, move set to bottom")
|
|||
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 [_]
|
||||
(and (valid-token-sets? sets) ;; TODO: validate set-groups
|
||||
(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.transit :as tr]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[clojure.data.json :as json]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/testing "token"
|
||||
|
@ -104,7 +105,25 @@
|
|||
|
||||
(t/testing "ignore invalid moves"
|
||||
(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/deftest make-token-theme
|
||||
|
@ -1026,3 +1045,94 @@
|
|||
(t/is (= (ctob/theme-count tokens-lib') 1))
|
||||
(t/is (= (count themes-tree') 1))
|
||||
(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))))))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue