0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-21 06:02:32 -05:00

Merge branch 'token-studio-develop' into json-dtcg-format

This commit is contained in:
Akshay Gupta 2024-06-26 17:47:54 +05:30 committed by GitHub
commit bf1c9e2c18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 468 additions and 204 deletions

View file

@ -26,6 +26,10 @@
"test:run": "node target/tests.cjs",
"test:watch": "clojure -M:dev:shadow-cljs watch test",
"test": "yarn run test:compile && yarn run test:run",
"token-test:compile": "clojure -M:dev:shadow-cljs compile test-esm --config-merge '{:autorun false}'",
"token-test:run": "bun target/tests-esm.cjs",
"token-test:watch": "clojure -M:dev:shadow-cljs watch test-esm",
"token-test": "yarn run token-test:compile && yarn run token-test:run",
"translations:validate": "node ./scripts/validate-translations.js",
"translations:find-unused": "node ./scripts/find-unused-translations.js",
"compile": "node ./scripts/compile.js",

View file

@ -106,8 +106,8 @@
:warnings {:fn-deprecated false}}}
:lib-penpot
{:target :esm
:output-dir "resources/public/libs"
{:target :esm
:output-dir "resources/public/libs"
:modules
{:penpot {:exports {:renderPage app.libs.render/render-page-export
@ -158,5 +158,17 @@
:source-map-detail-level :all
:warnings {:fn-deprecated false}}}
}}
:test-esm
{:target :node-test
:output-to "target/tests-esm.cjs"
:output-dir "target/test-esm"
:ns-regexp "^token-tests.*-test$"
:autorun true
:compiler-options
{:output-feature-set :es2020
:output-wrapper false
:source-map true
:source-map-include-sources-content true
:source-map-detail-level :all
:warnings {:fn-deprecated false}}}}}

View file

@ -2,6 +2,9 @@
Add changes that are meaningful to the user here after each PR so they can be updated in feature base.
<details>
<summary>Template</summary>
## Template
### <DATE> - <CHANGE_DESCRIPTION>
@ -11,14 +14,28 @@ Add changes that are meaningful to the user here after each PR so they can be up
If possible add video here from PR as well
- Outline of changes
</details>
## Changes
json-dtcg-format
### 2024-06-26 - Make Tokens JSON Export DTCG compatible
![Screenshot of sample JSON Export in DTCG format](https://private-user-images.githubusercontent.com/9948167/343043570-b4bb39f7-ec53-409a-a053-b284d60848d9.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTk0MDMyMzcsIm5iZiI6MTcxOTQwMjkzNywicGF0aCI6Ii85OTQ4MTY3LzM0MzA0MzU3MC1iNGJiMzlmNy1lYzUzLTQwOWEtYTA1My1iMjg0ZDYwODQ4ZDkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDYyNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA2MjZUMTE1NTM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MWEzZTU5OWQ0M2JkZWE5MTA5MDc4MTY1OTkyZWE5MmE5YzBlYmQ2NTcwMmEwZTdmMjViNGU5YTFjNWIxYjU5ZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.qWJxRa_Y7LZ6EDJg5yPdOUIQkURFmZwMNec_BbdH9Co)
https://github.com/tokens-studio/tokens-studio-for-penpot/issues/197
### 2024-06-25 - Token Insert/Edit Validation + Value Preview
[Video](https://private-user-images.githubusercontent.com/1898374/342781533-06054a7e-3efb-4f48-a063-8b03f4b8fe5c.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTkzMjgwNzYsIm5iZiI6MTcxOTMyNzc3NiwicGF0aCI6Ii8xODk4Mzc0LzM0Mjc4MTUzMy0wNjA1NGE3ZS0zZWZiLTRmNDgtYTA2My04YjAzZjRiOGZlNWMubXA0P1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDYyNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA2MjVUMTUwMjU2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDliZmUwMzU1MWY3NWQ2NWZkYzA0ODYxYzYzMTYzMjMyOGZjZGMzZDNhMWJmZGI4ZmM3NmU2NzNjYjY2MTdmMCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.44rKA1h3Cvw-vDWevnx7xVUeuZ1ezV4pqEtekVXgVds)
https://github.com/tokens-studio/tokens-studio-for-penpot/pull/194
Adds validation to the token create/edit field
- Name duplication is not allowed and takes a min/max length
- Value has to be a resolvable value
- Description has max value
### 2024-06-19 - Added CHANGELOG.md
Added template for changelog

View file

@ -34,7 +34,7 @@
(mf/defc labeled-input
{::mf/wrap-props false}
[{:keys [label input-props auto-complete?]}]
[{:keys [label input-props auto-complete? error?]}]
(let [input-props (cond-> input-props
:always camel-keys
;; Disable auto-complete on form fields for proprietary password managers
@ -42,6 +42,7 @@
(not auto-complete?) (assoc "data-1p-ignore" true
"data-lpignore" true
:auto-complete "off"))]
[:label {:class (stl/css :labeled-input)}
[:label {:class (stl/css-case :labeled-input true
:labeled-input-error error?)}
[:span {:class (stl/css :label)} label]
[:& :input input-props]]))

View file

@ -18,6 +18,10 @@
}
}
.labeled-input-error {
border: 1px solid var(--status-color-error-500) !important;
}
.button {
@extend .button-primary;
}

View file

@ -0,0 +1,265 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.tokens.form
(:require-macros [app.main.style :as stl])
(:require
["lodash.debounce" :as debounce]
[app.main.data.modal :as modal]
[app.main.data.tokens :as dt]
[app.main.store :as st]
[app.main.ui.workspace.tokens.common :as tokens.common]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.util.dom :as dom]
[cuerdas.core :as str]
[malli.core :as m]
[malli.error :as me]
[promesa.core :as p]
[rumext.v2 :as mf]))
;; Schemas ---------------------------------------------------------------------
(defn token-name-schema
"Generate a dynamic schema validation to check if a token name already exists.
`existing-token-names` should be a set of strings."
[existing-token-names]
(let [non-existing-token-schema
(m/-simple-schema
{:type :token/name-exists
:pred #(not (get existing-token-names %))
:type-properties {:error/fn #(str (:value %) " is an already existing token name")
:existing-token-names existing-token-names}})]
(m/schema
[:and
[:string {:min 1 :max 255}]
non-existing-token-schema])))
(def token-description-schema
(m/schema
[:string {:max 2048}]))
;; Helpers ---------------------------------------------------------------------
(defn finalize-name [name]
(str/trim name))
(defn valid-name? [name]
(seq (finalize-name (str name))))
(defn finalize-value [value]
(-> (str value)
(str/trim)))
(defn valid-value? [value]
(seq (finalize-value value)))
(defn schema-validation->promise [validated]
(if (:errors validated)
(p/rejected validated)
(p/resolved validated)))
;; Component -------------------------------------------------------------------
(defn validate-token-value+
"Validates token value by resolving the value `input` using `StyleDictionary`.
Returns a promise of either resolved tokens or rejects with an error state."
[{:keys [input name-value token tokens]}]
(let [empty-input? (empty? (str/trim input))
;; Check if the given value contains a reference that is the current token-name
;; When creating a new token we dont have a token name yet,
;; so we use a temporary token name that hopefully doesn't clash with any of the users token names.
token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value)
token-references (sd/find-token-references input)
direct-self-reference? (get token-references token-name)]
(cond
empty-input? (p/rejected nil)
direct-self-reference? (p/rejected :error/token-direct-self-reference)
:else (let [token-id (or (:id token) (random-uuid))
new-tokens (update tokens token-id merge {:id token-id
:value input
:name token-name})]
(-> (sd/resolve-tokens+ new-tokens)
(p/then
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)]
(cond
resolved-value (p/resolved resolved-token)
(sd/missing-reference-error? errors) (p/rejected :error/token-missing-reference)
:else (p/rejected :error/unknown-error))))))))))
(defn use-debonced-resolve-callback
"Resolves a token values using `StyleDictionary`.
This function is debounced as the resolving might be an expensive calculation.
Uses a custom debouncing logic, as the resolve function is async."
[name-ref token tokens callback & {:keys [timeout] :or {timeout 160}}]
(let [timeout-id-ref (mf/use-ref nil)
debounced-resolver-callback
(mf/use-callback
(mf/deps token callback tokens)
(fn [event]
(let [input (dom/get-target-val event)
timeout-id (js/Symbol)
;; Dont execute callback when the timout-id-ref is outdated because this function got called again
timeout-outdated-cb? #(not= (mf/ref-val timeout-id-ref) timeout-id)]
(mf/set-ref-val! timeout-id-ref timeout-id)
(js/setTimeout
(fn []
(when (not (timeout-outdated-cb?))
(-> (validate-token-value+ {:input input
:name-value @name-ref
:token token
:tokens tokens})
(p/finally (fn [x err]
(when-not (timeout-outdated-cb?)
(callback (or err x))))))))
timeout))))]
debounced-resolver-callback))
(mf/defc form
{::mf/wrap-props false}
[{:keys [token token-type] :as _args}]
(let [tokens (sd/use-resolved-workspace-tokens)
existing-token-names (mf/use-memo
(mf/deps tokens)
(fn []
(-> (into #{} (map (fn [[_ {:keys [name]}]] name) tokens))
;; Remove the currently editing token name,
;; as we don't want it to show when checking for duplicate names.
(disj (:name token)))))
;; Name
name-ref (mf/use-var (:name token))
name-errors (mf/use-state nil)
validate-name (mf/use-callback
(mf/deps existing-token-names)
(fn [value]
(let [schema (token-name-schema existing-token-names)]
(m/explain schema (finalize-name value)))))
on-update-name-debounced (mf/use-callback
(debounce (fn [e]
(let [value (dom/get-target-val e)
errors (validate-name value)]
(reset! name-errors errors)))))
on-update-name (mf/use-callback
(mf/deps on-update-name-debounced)
(fn [e]
(reset! name-ref (dom/get-target-val e))
(on-update-name-debounced e)))
valid-name-field? (and
(not @name-errors)
(valid-name? @name-ref))
;; Value
value-ref (mf/use-var (:value token))
token-resolve-result (mf/use-state (get-in tokens [(:id token) :resolved-value]))
set-resolve-value (mf/use-callback
(fn [token-or-err]
(let [v (cond
(= token-or-err :error/token-direct-self-reference) token-or-err
(= token-or-err :error/token-missing-reference) token-or-err
(:resolved-value token-or-err) (:resolved-value token-or-err))]
(reset! token-resolve-result v))))
on-update-value-debounced (use-debonced-resolve-callback name-ref token tokens set-resolve-value)
on-update-value (mf/use-callback
(mf/deps on-update-value-debounced)
(fn [e]
(reset! value-ref (dom/get-target-val e))
(on-update-value-debounced e)))
value-error? (when (keyword? @token-resolve-result)
(= (namespace @token-resolve-result) "error"))
valid-value-field? (and
(not value-error?)
(valid-value? @token-resolve-result))
;; Description
description-ref (mf/use-var (:description token))
description-errors (mf/use-state nil)
validate-descripion (mf/use-callback #(m/explain token-description-schema %))
on-update-description-debounced (mf/use-callback
(debounce (fn [e]
(let [value (dom/get-target-val e)
errors (validate-descripion value)]
(reset! description-errors errors)))))
on-update-description (mf/use-callback
(mf/deps on-update-description-debounced)
(fn [e]
(reset! description-ref (dom/get-target-val e))
(on-update-description-debounced e)))
valid-description-field? (not @description-errors)
;; Form
disabled? (or (not valid-name-field?)
(not valid-value-field?)
(not valid-description-field?))
on-submit (mf/use-callback
(mf/deps validate-name validate-descripion token tokens)
(fn [e]
(dom/prevent-default e)
;; We have to re-validate the current form values before submitting
;; because the validation is asynchronous/debounced
;; and the user might have edited a valid form to make it invalid,
;; and press enter before the next validations could return.
(let [final-name (finalize-name @name-ref)
valid-name?+ (-> (validate-name final-name) schema-validation->promise)
final-value (finalize-value @value-ref)
final-description @description-ref
valid-description?+ (some-> final-description validate-descripion schema-validation->promise)]
(-> (p/all [valid-name?+
valid-description?+
(validate-token-value+ {:input final-value
:name-value final-name
:token token
:tokens tokens})])
(p/finally (fn [result err]
;; The result should be a vector of all resolved validations
;; We do not handle the error case as it will be handled by the components validations
(when (and (seq result) (not err))
(let [token (cond-> {:name final-name
:type (or (:type token) token-type)
:value final-value}
final-description (assoc :description final-description)
(:id token) (assoc :id (:id token)))]
(st/emit! (dt/add-token token))
(modal/hide!)))))))))]
[:form
{:on-submit on-submit}
[:div {:class (stl/css :token-rows)}
[:div
[:& tokens.common/labeled-input {:label "Name"
:error? @name-errors
:input-props {:default-value @name-ref
:auto-focus true
:on-blur on-update-name
:on-change on-update-name}}]
(when @name-errors
[:p {:class (stl/css :error)}
(me/humanize @name-errors)])]
[:& tokens.common/labeled-input {:label "Value"
:input-props {:default-value @value-ref
:on-blur on-update-value
:on-change on-update-value}}]
[:div {:class (stl/css-case :resolved-value true
:resolved-value-placeholder (nil? @token-resolve-result)
:resolved-value-error value-error?)}
(case @token-resolve-result
:error/token-direct-self-reference "Token has self reference"
:error/token-missing-reference "Token has missing reference"
:error/unknown-error ""
nil "Enter token value"
[:p @token-resolve-result])]
[:div
[:& tokens.common/labeled-input {:label "Description"
:input-props {:default-value @description-ref
:on-change on-update-description}}]
(when @description-errors
[:p {:class (stl/css :error)}
(me/humanize @description-errors)])]
[:div {:class (stl/css :button-row)}
[:button {:class (stl/css :button)
:type "submit"
:disabled disabled?}
"Save"]]]]))

View file

@ -0,0 +1,50 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@import "refactor/common-refactor.scss";
@import "./common.scss";
.button-row {
display: flex;
flex-direction: column;
margin-top: $s-16;
}
.token-rows {
display: flex;
flex-direction: column;
gap: $s-8;
}
.error {
@include bodySmallTypography;
margin-top: $s-6;
margin-bottom: 0;
color: var(--status-color-error-500);
}
.resolved-value {
@include bodySmallTypography;
padding: $s-4 $s-6;
font-weight: medium;
height: $s-24;
color: var(--color-foreground-primary);
border: 1px solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent);
p {
font-size: $fs-12;
margin: 0;
}
}
.resolved-value-placeholder {
color: var(--color-foreground-secondary);
}
.resolved-value-error {
color: var(--status-color-error-500);
}

View file

@ -1,102 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.tokens.modal
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.data.modal :as modal]
[app.main.data.tokens :as dt]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.workspace.tokens.common :as tokens.common]
[app.util.dom :as dom]
[okulary.core :as l]
[rumext.v2 :as mf]))
(defn calculate-position
"Calculates the style properties for the given coordinates and position"
[{vh :height} position x y]
(let [;; picker height in pixels
h 510
;; Checks for overflow outside the viewport height
overflow-fix (max 0 (+ y (- 50) h (- vh)))
x-pos 325]
(cond
(or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"}
(= position :left) {:left (str (- x x-pos) "px")
:top (str (- y 50 overflow-fix) "px")}
:else {:left (str (+ x 80) "px")
:top (str (- y 70 overflow-fix) "px")})))
(def viewport
(l/derived :vport refs/workspace-local))
(defn fields->map [fields]
(->> (map (fn [{:keys [key] :as field}]
[key (:value field)]) fields)
(into {})))
(mf/defc tokens-properties-form
{::mf/wrap-props false}
[{:keys [token-type x y position fields token]}]
(let [vport (mf/deref viewport)
style (calculate-position vport position x y)
name (mf/use-var (or (:name token) ""))
on-update-name #(reset! name (dom/get-target-val %))
token-value (mf/use-var (or (:value token) ""))
description (mf/use-var (or (:description token) ""))
on-update-description #(reset! description (dom/get-target-val %))
initial-fields (mapv (fn [field]
(assoc field :value (or (:value token) "")))
fields)
state (mf/use-state initial-fields)
on-update-state-field (fn [idx e]
(let [value (dom/get-target-val e)]
(swap! state assoc-in [idx :value] value)))
on-submit (fn [e]
(dom/prevent-default e)
(let [token-value (-> (fields->map @state)
(first)
(val))
token (cond-> {:name @name
:type (or (:type token) token-type)
:value token-value}
@description (assoc :description @description)
(:id token) (assoc :id (:id token)))]
(st/emit! (dt/add-token token))
(modal/hide!)))]
[:form
{:class (stl/css :shadow)
:style (clj->js style)
:on-submit on-submit}
[:div {:class (stl/css :token-rows)}
[:& tokens.common/labeled-input {:label "Name"
:input-props {:default-value @name
:auto-focus true
:on-change on-update-name}}]
(for [[idx {:keys [type label]}] (d/enumerate @state)]
[:* {:key (str "form-field-" idx)}
(case type
:box-shadow [:p "TODO BOX SHADOW"]
[:& tokens.common/labeled-input {:label label
:input-props {:default-value @token-value
:on-change #(on-update-state-field idx %)}}])])
[:& tokens.common/labeled-input {:label "Description"
:input-props {:default-value @description
:on-change #(on-update-description %)}}]
[:div {:class (stl/css :button-row)}
[:button {:class (stl/css :button)
:type "submit"}
"Save"]]]]))

View file

@ -1,66 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@import "refactor/common-refactor.scss";
@import "./common.scss";
.button-row {
display: flex;
flex-direction: column;
margin-top: $s-16;
}
.token-rows {
display: flex;
flex-direction: column;
gap: $s-8;
}
.shadow {
@extend .modal-container-base;
@include menuShadow;
position: absolute;
z-index: 11;
overflow-y: auto;
overflow-x: hidden;
&-select-wrapper {
display: flex;
grid-gap: $s-4;
}
&-properties {
display: flex;
flex-direction: column;
grid-gap: $s-4;
}
.inputs-grid {
display: grid;
grid-template-areas:
"x blur blur spread spread"
"y color color color color";
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-gap: $s-4;
label:nth-child(1) {
grid-area: x;
}
label:nth-child(2) {
grid-area: y;
}
label:nth-child(3) {
grid-area: blur;
}
label:nth-child(4) {
grid-area: spread;
}
label:nth-child(5) {
grid-area: color;
}
}
}

View file

@ -5,85 +5,124 @@
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.tokens.modals
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.modal :as modal]
[app.main.ui.workspace.tokens.modal :refer [tokens-properties-form]]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.form :refer [form]]
[okulary.core :as l]
[rumext.v2 :as mf]))
;; Component -------------------------------------------------------------------
(defn calculate-position
"Calculates the style properties for the given coordinates and position"
[{vh :height} position x y]
(let [;; picker height in pixels
h 510
;; Checks for overflow outside the viewport height
overflow-fix (max 0 (+ y (- 50) h (- vh)))
x-pos 325]
(cond
(or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"}
(= position :left) {:left (str (- x x-pos) "px")
:top (str (- y 50 overflow-fix) "px")}
:else {:left (str (+ x 80) "px")
:top (str (- y 70 overflow-fix) "px")})))
(defn use-viewport-position-style [x y position]
(let [vport (-> (l/derived :vport refs/workspace-local)
(mf/deref))]
(-> (calculate-position vport position x y)
(clj->js))))
(mf/defc modal
{::mf/wrap-props false}
[{:keys [x y position token token-type] :as _args}]
(let [wrapper-style (use-viewport-position-style x y position)]
[:div
{:class (stl/css :shadow)
:style wrapper-style}
[:& form {:token token
:token-type token-type}]]))
;; Modals ----------------------------------------------------------------------
(mf/defc boolean-modal
{::mf/register modal/components
::mf/register-as :tokens/boolean}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc border-radius-modal
{::mf/register modal/components
::mf/register-as :tokens/border-radius}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc stroke-width-modal
{::mf/register modal/components
::mf/register-as :tokens/stroke-width}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc box-shadow-modal
{::mf/register modal/components
::mf/register-as :tokens/box-shadow}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc sizing-modal
{::mf/register modal/components
::mf/register-as :tokens/sizing}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc dimensions-modal
{::mf/register modal/components
::mf/register-as :tokens/dimensions}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc numeric-modal
{::mf/register modal/components
::mf/register-as :tokens/numeric}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc opacity-modal
{::mf/register modal/components
::mf/register-as :tokens/opacity}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc other-modal
{::mf/register modal/components
::mf/register-as :tokens/other}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc rotation-modal
{::mf/register modal/components
::mf/register-as :tokens/rotation}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc spacing-modal
{::mf/register modal/components
::mf/register-as :tokens/spacing}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc string-modal
{::mf/register modal/components
::mf/register-as :tokens/string}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])
(mf/defc typography-modal
{::mf/register modal/components
::mf/register-as :tokens/typography}
[properties]
[:& tokens-properties-form properties])
[:& modal properties])

View file

@ -0,0 +1,16 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@import "refactor/common-refactor.scss";
.shadow {
@extend .modal-container-base;
@include menuShadow;
position: absolute;
z-index: 11;
overflow-y: auto;
overflow-x: hidden;
}

View file

@ -4,6 +4,7 @@
["style-dictionary$default" :as sd]
[app.common.data :as d]
[app.main.refs :as refs]
[app.util.dom :as dom]
[cuerdas.core :as str]
[promesa.core :as p]
[rumext.v2 :as mf]
@ -16,12 +17,19 @@
(do
(sd-transforms/registerTransforms sd)
(.registerFormat sd #js {:name "custom/json"
:format (fn [res]
:format (fn [^js res]
(.-tokens (.-dictionary res)))})
sd))
;; Functions -------------------------------------------------------------------
(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 tokens->style-dictionary+
"Resolves references and math expressions using StyleDictionary.
Returns a promise with the resolved dictionary."
@ -69,6 +77,11 @@
errors)
(str/join "\n")))
(defn missing-reference-error?
[errors]
(and (set? errors)
(get errors :style-dictionary/missing-reference)))
(defn tokens-name-map [tokens]
(->> tokens
(map (fn [[_ x]] [(:name x) x]))
@ -77,7 +90,6 @@
(defn resolve-tokens+
[tokens & {:keys [debug?] :as config}]
(p/let [sd-tokens (-> (tokens-name-map tokens)
(clj->js)
(resolve-sd-tokens+ config))]
(let [resolved-tokens (reduce
(fn [acc ^js cur]
@ -129,25 +141,15 @@
(reset! tokens-state resolved-tokens))))))))
@tokens-state))
(defn use-resolved-workspace-tokens
([] (use-resolved-tokens nil))
([options]
(-> (mf/deref refs/workspace-tokens)
(use-resolved-tokens options))))
(defn use-resolved-workspace-tokens [& {:as config}]
(-> (mf/deref refs/workspace-tokens)
(use-resolved-tokens config)))
;; Testing ---------------------------------------------------------------------
(defn tokens-studio-example []
(-> (shadow.resource/inline "./data/example-tokens-set.json")
(js/JSON.parse)
.-core))
(comment
(defonce !output (atom nil))
@!output
(-> (resolve-workspace-tokens+ {:debug? true})
(p/then #(reset! !output %)))
@ -159,7 +161,9 @@
"b" {:name "b" :value "{a} * 2"}})
(#(resolve-sd-tokens+ % {:debug? true})))
(-> (tokens-studio-example)
(resolve-sd-tokens+ {:debug? true}))
(let [example (-> (shadow.resource/inline "./data/example-tokens-set.json")
(js/JSON.parse)
.-core)]
(resolve-sd-tokens+ example {:debug? true}))
nil)

View file

@ -0,0 +1,20 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns token-tests.style-dictionary-test
(:require
[app.main.ui.workspace.tokens.style-dictionary :as wtsd]
[cljs.test :as t :include-macros true]))
(t/deftest test-find-token-references
;; Return references
(t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo} + {bar}")))
;; Ignore non reference text
(t/is (= #{"foo.bar.baz"} (wtsd/find-token-references "{foo.bar.baz} + something")))
;; No references found
(t/is (nil? (wtsd/find-token-references "1 + 2")))
;; Edge-case: Ignore unmatched closing parens
(t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo}} + {bar}"))))