0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-13 07:21:40 -05:00

Merge pull request #5766 from penpot/niwinz-tokens-changes

 Several changes to tokens
This commit is contained in:
Andrey Antukh 2025-02-13 11:46:58 +01:00 committed by GitHub
commit 054efb3435
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 440 additions and 330 deletions

View file

@ -312,7 +312,7 @@
(ptk/reify ::set-token-type-section-open
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-tokens :open-status token-type] open?))))
(assoc-in state [:workspace-local :token-type-open-status token-type] open?))))
;; === Token Context Menu

View file

@ -1,8 +0,0 @@
(ns app.main.data.workspace.tokens.common
(:require
[app.main.data.helpers :as dsh]))
(defn get-workspace-tokens-lib
[state]
(-> (dsh/lookup-file-data state)
(get :tokens-lib)))

View file

@ -1,24 +1,29 @@
;; 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.data.workspace.tokens.selected-set
"The user selected token set in the ui, stored by the `:name` of the set.
Will default to the first set."
(:require
[app.common.types.tokens-lib :as ctob]
[app.main.data.workspace.tokens.common :as dwtc]
[app.main.data.helpers :as dsh]
[potok.v2.core :as ptk]))
(defn assoc-selected-token-set-name [state set-name]
(assoc-in state [:workspace-local :selected-token-set-name] set-name))
(defn get-selected-token-set-name [state]
(or (get-in state [:workspace-local :selected-token-set-name])
(some-> (dwtc/get-workspace-tokens-lib state)
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-sets)
(first)
:name)))
(defn get-selected-token-set [state]
(when-let [set-name (get-selected-token-set-name state)]
(some-> (dwtc/get-workspace-tokens-lib state)
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-set set-name))))
(defn get-selected-token-set-token [state token-name]
@ -30,8 +35,8 @@
:tokens))
(defn set-selected-token-set-name
[set-name]
[name]
(ptk/reify ::set-selected-token-set-path-from-name
ptk/UpdateEvent
(update [_ state]
(assoc-selected-token-set-name state set-name))))
(update state :workspace-local assoc :selected-token-set-name name))))

View file

@ -144,7 +144,7 @@
(l/derived
(fn [{:keys [objects selected]}]
(dsh/process-selected-shapes objects selected))
selected-shapes-data))
selected-shapes-data =))
(defn make-selected-ref
[id]
@ -479,6 +479,7 @@
(def workspace-active-set-names
(l/derived (d/nilf ctob/get-active-themes-set-names) tokens-lib))
;; FIXME: deprecated, it should not be implemented with ref
(def workspace-active-theme-sets-tokens
(l/derived #(or (some-> % ctob/get-active-themes-set-tokens) {}) tokens-lib))

View file

@ -31,7 +31,7 @@
[app.main.ui.workspace.sidebar.shortcuts :refer [shortcuts-container]]
[app.main.ui.workspace.sidebar.sitemap :refer [sitemap]]
[app.main.ui.workspace.sidebar.versions :refer [versions-toolbox*]]
[app.main.ui.workspace.tokens.sidebar :refer [tokens-sidebar-tab]]
[app.main.ui.workspace.tokens.sidebar :refer [tokens-sidebar-tab*]]
[app.util.debug :as dbg]
[app.util.i18n :refer [tr]]
[potok.v2.core :as ptk]
@ -120,7 +120,7 @@
tokens-tab
(when design-tokens?
(mf/html [:& tokens-sidebar-tab]))
(mf/html [:> tokens-sidebar-tab*]))
tabs
(if ^boolean mode-inspect?

View file

@ -6,6 +6,8 @@
(ns app.main.ui.workspace.tokens.changes
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.shape.layout :as ctsl]
[app.common.types.shape.radius :as ctsr]
[app.common.types.token :as ctt]
@ -26,6 +28,8 @@
[clojure.set :as set]
[potok.v2.core :as ptk]))
(declare token-properties)
;; Token Updates ---------------------------------------------------------------
(defn apply-token
@ -76,12 +80,16 @@
(update shape :applied-tokens remove-token))))))))
(defn toggle-token
[{:keys [token-type-props token shapes] :as _props}]
[{:keys [token shapes]}]
(ptk/reify ::on-toggle-token
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [attributes all-attributes on-update-shape]} token-type-props
unapply-tokens? (wtt/shapes-token-applied? token shapes (or all-attributes attributes))
(let [{:keys [attributes all-attributes on-update-shape]}
(get token-properties (:type token))
unapply-tokens?
(wtt/shapes-token-applied? token shapes (or all-attributes attributes))
shape-ids (map :id shapes)]
(if unapply-tokens?
(rx/of
@ -272,3 +280,88 @@
(select-keys attributes))]
(dwsl/update-layout-child shape-ids props {:ignore-touched true
:page-id page-id}))))))
;; Token Types -----------------------------------------------------------------
;; FIXME: the values should be lazy evaluated, probably a function,
;; becasue on future we will need to translate that labels and that
;; can not be done statically
(def token-properties
"A map of default properties by token type"
(d/ordered-map
:border-radius
{:title "Border Radius"
:attributes ctt/border-radius-keys
:on-update-shape update-shape-radius-all
:modal {:key :tokens/border-radius
:fields [{:label "Border Radius"
:key :border-radius}]}}
:color
{:title "Color"
:attributes #{:fill}
:all-attributes ctt/color-keys
:on-update-shape update-fill-stroke
:modal {:key :tokens/color
:fields [{:label "Color" :key :color}]}}
:stroke-width
{:title "Stroke Width"
:attributes ctt/stroke-width-keys
:on-update-shape update-stroke-width
:modal {:key :tokens/stroke-width
:fields [{:label "Stroke Width"
:key :stroke-width}]}}
:sizing
{:title "Sizing"
:attributes #{:width :height}
:all-attributes ctt/sizing-keys
:on-update-shape update-shape-dimensions
:modal {:key :tokens/sizing
:fields [{:label "Sizing"
:key :sizing}]}}
:dimensions
{:title "Dimensions"
:attributes #{:width :height}
:all-attributes (set/union
ctt/spacing-keys
ctt/sizing-keys
ctt/border-radius-keys
ctt/stroke-width-keys)
:on-update-shape update-shape-dimensions
:modal {:key :tokens/dimensions
:fields [{:label "Dimensions"
:key :dimensions}]}}
:opacity
{:title "Opacity"
:attributes ctt/opacity-keys
:on-update-shape update-opacity
:modal {:key :tokens/opacity
:fields [{:label "Opacity"
:key :opacity}]}}
:rotation
{:title "Rotation"
:attributes ctt/rotation-keys
:on-update-shape update-rotation
:modal {:key :tokens/rotation
:fields [{:label "Rotation"
:key :rotation}]}}
:spacing
{:title "Spacing"
:attributes #{:column-gap :row-gap}
:all-attributes ctt/spacing-keys
:on-update-shape update-layout-spacing
:modal {:key :tokens/spacing
:fields [{:label "Spacing"
:key :spacing}]}}))
(defn get-token-properties [token]
(get token-properties (:type token)))
(defn token-attributes [token-type]
(dm/get-in token-properties [token-type :attributes]))

View file

@ -18,7 +18,6 @@
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.timers :as timers]
@ -38,7 +37,7 @@
(defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape]}]
(let [on-update-shape-fn (or on-update-shape
(-> (wtty/get-token-properties token)
(-> (wtch/get-token-properties token)
(:on-update-shape)))
{:keys [selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)]
(map (fn [attribute]
@ -236,7 +235,7 @@
(generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))}))
(defn default-actions [{:keys [token selected-token-set-name]}]
(let [{:keys [modal]} (wtty/get-token-properties token)]
(let [{:keys [modal]} (wtch/get-token-properties token)]
[{:title (tr "workspace.token.edit")
:no-selectable true
:action (fn [event]

View file

@ -21,13 +21,13 @@
[app.main.ui.notifications.context-notification :refer [context-notification]]
[app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.components.controls.input-token-color-bullet :refer [input-token-color-bullet*]]
[app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]]
[app.main.ui.workspace.tokens.errors :as wte]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.main.ui.workspace.tokens.update :as wtu]
[app.main.ui.workspace.tokens.warnings :as wtw]
[app.util.dom :as dom]
@ -223,7 +223,7 @@
[{:keys [token token-type action selected-token-set-name on-display-colorpicker]}]
(let [create? (not (instance? ctob/Token token))
token (or token {:type token-type})
token-properties (wtty/get-token-properties token)
token-properties (wtch/get-token-properties token)
color? (wtt/color-token? token)
selected-set-tokens (mf/deref refs/workspace-selected-token-set-tokens)
active-theme-tokens (mf/deref refs/workspace-active-theme-sets-tokens)

View file

@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
@ -18,6 +19,7 @@
[app.main.ui.components.dropdown-menu :refer [dropdown-menu
dropdown-menu-item*]]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
@ -32,9 +34,8 @@
[app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu]]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.theme-select :refer [theme-select]]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-pill :refer [token-pill]]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.main.ui.workspace.tokens.token-pill :refer [token-pill*]]
[app.util.array :as array]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.webapi :as wapi]
@ -44,8 +45,8 @@
[rumext.v2 :as mf]
[shadow.resource]))
(def lens:token-type-open-status
(l/derived (l/in [:workspace-tokens :open-status]) st/state))
(def ref:token-type-open-status
(l/derived #(dm/get-in % [:workspace-local :token-type-open-status]) st/state))
;; Components ------------------------------------------------------------------
@ -65,62 +66,64 @@
:sizing "expand"
"add"))
(defn attribute-actions [token selected-shapes attributes]
(let [ids-by-attributes (wtt/shapes-ids-by-applied-attributes token selected-shapes attributes)
shape-ids (into #{} (map :id selected-shapes))]
{:all-selected? (wtt/shapes-applied-all? ids-by-attributes shape-ids attributes)
:shape-ids shape-ids
:selected-pred #(seq (% ids-by-attributes))}))
(mf/defc token-group*
{::mf/private true}
[{:keys [type tokens selected-shapes active-theme-tokens is-open]}]
(let [{:keys [modal title]}
(get wtch/token-properties type)
(mf/defc token-component
[{:keys [type tokens selected-shapes token-type-props active-theme-tokens]}]
(let [open? (mf/deref (-> (l/key type)
(l/derived lens:token-type-open-status)))
{:keys [modal attributes all-attributes title]} token-type-props
tokens
(mf/with-memo [tokens]
(vec (sort-by :name tokens)))
on-context-menu (mf/use-fn
(fn [event token]
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (dt/show-token-context-menu
{:type :token
:position (dom/get-client-position event)
:errors (:errors token)
:token-name (:name token)}))))
on-context-menu
(mf/use-fn
(fn [event token]
(dom/prevent-default event)
(dom/stop-propagation event)
(st/emit! (dt/show-token-context-menu
{:type :token
:position (dom/get-client-position event)
:errors (:errors token)
:token-name (:name token)}))))
on-toggle-open-click (mf/use-fn
(mf/deps open? tokens)
#(st/emit! (dt/set-token-type-section-open type (not open?))))
on-popover-open-click (mf/use-fn
(fn [event]
(mf/deps type title)
(let [{:keys [key fields]} modal]
(dom/stop-propagation event)
(st/emit! (dt/set-token-type-section-open type true))
(modal/show! key {:x (.-clientX ^js event)
:y (.-clientY ^js event)
:position :right
:fields fields
:title title
:action "create"
:token-type type}))))
on-toggle-open-click
(mf/use-fn
(mf/deps is-open type)
#(st/emit! (dt/set-token-type-section-open type (not is-open))))
on-token-pill-click (mf/use-fn
(mf/deps selected-shapes token-type-props)
(fn [event token]
(dom/stop-propagation event)
(when (seq selected-shapes)
(st/emit!
(wtch/toggle-token {:token token
:shapes selected-shapes
:token-type-props token-type-props})))))
on-popover-open-click
(mf/use-fn
(mf/deps type title modal)
(fn [event]
(dom/stop-propagation event)
(st/emit! (dt/set-token-type-section-open type true)
;; FIXME: use dom/get-client-position
(modal/show (:key modal)
{:x (.-clientX ^js event)
:y (.-clientY ^js event)
:position :right
:fields (:fields modal)
:title title
:action "create"
:token-type type}))))
on-token-pill-click
(mf/use-fn
(mf/deps selected-shapes)
(fn [event token]
(dom/stop-propagation event)
(when (seq selected-shapes)
(st/emit! (wtch/toggle-token {:token token
:shapes selected-shapes})))))
tokens-count (count tokens)
can-edit? (:can-edit (deref refs/permissions))]
[:div {:on-click on-toggle-open-click}
[:& cmm/asset-section {:icon (token-section-icon type)
:title title
:assets-count tokens-count
:open? open?}
:open? is-open}
[:& cmm/asset-section-block {:role :title-button}
(when can-edit?
[:> icon-button* {:on-click on-popover-open-click
@ -128,56 +131,55 @@
:icon "add"
;; TODO: This needs translation
:aria-label (str "Add token: " title)}])]
(when open?
(when is-open
[:& cmm/asset-section-block {:role :content}
[:div {:class (stl/css :token-pills-wrapper)}
(for [token (sort-by :name tokens)]
(let [theme-token (get active-theme-tokens (wtt/token-identifier token))
multiple-selection (< 1 (count selected-shapes))
full-applied (:all-selected? (attribute-actions token selected-shapes (or all-attributes attributes)))
applied (wtt/shapes-token-applied? token selected-shapes (or all-attributes attributes))
on-token-click (fn [e]
(on-token-pill-click e token))
on-context-menu (fn [e] (on-context-menu e token))]
[:& token-pill
{:key (:name token)
:token-type-props token-type-props
:token token
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:theme-token theme-token
:half-applied (or (and applied multiple-selection)
(and applied (not full-applied)))
:full-applied (if multiple-selection
false
applied)
:on-click on-token-click
:on-context-menu on-context-menu}]))]])]]))
(for [token tokens]
[:> token-pill*
{:key (:name token)
:token token
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:on-click on-token-pill-click
:on-context-menu on-context-menu}])]])]]))
(defn sorted-token-groups
"Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type.
Sort each group alphabetically (by their `:token-key`)."
[tokens]
(let [tokens-by-type (ctob/group-by-type tokens)
{:keys [empty filled]} (->> wtty/token-types
(map (fn [[token-key token-type-props]]
{:token-key token-key
:token-type-props token-type-props
:tokens (get tokens-by-type token-key [])}))
(group-by (fn [{:keys [tokens]}]
(if (empty? tokens) :empty :filled))))]
{:empty (sort-by :token-key empty)
:filled (sort-by :token-key filled)}))
(defn- get-sorted-token-groups
"Separate token-types into groups of `empty` or `filled` depending if
tokens exist for that type. Sort each group alphabetically (by
their type)."
[tokens-by-type]
(loop [empty #js []
filled #js []
types (-> wtch/token-properties keys seq)]
(if-let [type (first types)]
(if (not-empty (get tokens-by-type type))
(recur empty
(array/conj! filled type)
(rest types))
(recur (array/conj! empty type)
filled
(rest types)))
[(seq (array/sort! empty))
(seq (array/sort! filled))])))
(mf/defc themes-header*
{::mf/private true}
[]
(let [ordered-themes
(mf/deref refs/workspace-token-themes-no-hidden)
permissions
(mf/use-ctx ctx/permissions)
can-edit?
(get permissions :can-edit)
(mf/defc themes-header
[_props]
(let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)
can-edit? (:can-edit (deref refs/permissions))
open-modal
(mf/use-fn
(fn [e]
(dom/stop-propagation e)
(modal/show! :tokens/themes {})))]
[:div {:class (stl/css :themes-wrapper)}
[:span {:class (stl/css :themes-header)} (tr "labels.themes")]
(if (empty? ordered-themes)
@ -199,13 +201,24 @@
(tr "workspace.token.no-permission-themes"))}
[:& theme-select]]))]))
(mf/defc add-set-button
[{:keys [on-open style]}]
(let [{:keys [on-create new-path]} (sets-context/use-context)
on-click #(do
(on-open)
(on-create []))
can-edit? (:can-edit (deref refs/permissions))]
(mf/defc add-set-button*
{::mf/private true}
[{:keys [style]}]
(let [{:keys [on-create new-path]}
(sets-context/use-context)
permissions
(mf/use-ctx ctx/permissions)
can-edit?
(get permissions :can-edit)
on-click
(mf/use-fn
(mf/deps on-create)
(fn []
(on-create [])))]
(if (= style "inline")
(when-not new-path
(if can-edit?
@ -224,67 +237,96 @@
:on-click on-click
:aria-label (tr "workspace.token.add set")}]))))
(mf/defc theme-sets-list
[{:keys [on-open]}]
(mf/defc theme-sets-list*
{::mf/private true}
[]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
{:keys [new-path] :as ctx} (sets-context/use-context)]
(if (and (empty? token-sets)
(not new-path))
[:& add-set-button {:on-open on-open
:style "inline"}]
[:> add-set-button* {:style "inline"}]
[:& h/sortable-container {}
[:& sets-list]])))
(mf/defc themes-sets-tab
(mf/defc themes-sets-tab*
{::mf/private true}
[{:keys [resize-height]}]
(let [open? (mf/use-state true)
on-open (mf/use-fn #(reset! open? true))
can-edit? (:can-edit (deref refs/permissions))]
(let [permissions
(mf/use-ctx ctx/permissions)
can-edit?
(get permissions :can-edit)]
[:& sets-context/provider {}
[:& sets-context-menu]
[:article {:data-testid "token-themes-sets-sidebar"
:class (stl/css :sets-section-wrapper)
:style {"--resize-height" (str resize-height "px")}}
[:div {:class (stl/css :sets-sidebar)}
[:& themes-header]
[:> themes-header*]
[:div {:class (stl/css :sidebar-header)}
[:& title-bar {:title (tr "labels.sets")}
(when can-edit?
[:& add-set-button {:on-open on-open
:style "header"}])]]
[:& theme-sets-list {:on-open on-open}]]]]))
[:> add-set-button* {:style "header"}])]]
(mf/defc tokens-tab
[_props]
(let [objects (mf/deref refs/workspace-page-objects)
[:> theme-sets-list* {}]]]]))
selected (mf/deref refs/selected-shapes)
selected-shapes (into [] (keep (d/getf objects)) selected)
(mf/defc tokens-tab*
[]
(let [objects (mf/deref refs/workspace-page-objects)
selected (mf/deref refs/selected-shapes)
open-status (mf/deref ref:token-type-open-status)
active-theme-tokens (sd/use-active-theme-sets-tokens)
selected-shapes
(mf/with-memo [selected objects]
(into [] (keep (d/getf objects)) selected))
tokens (sd/use-resolved-workspace-tokens)
active-theme-tokens
(sd/use-active-theme-tokens)
selected-token-set-tokens (mf/deref refs/workspace-selected-token-set-tokens)
tokens
(sd/use-resolved-workspace-tokens)
selected-token-set-name (mf/deref refs/workspace-selected-token-set-name)
selected-token-set-tokens
(mf/deref refs/workspace-selected-token-set-tokens)
selected-token-set-name
(mf/deref refs/workspace-selected-token-set-name)
tokens-by-type
(mf/with-memo [tokens selected-token-set-tokens]
(let [tokens (reduce-kv (fn [tokens k _]
(if (contains? selected-token-set-tokens k)
tokens
(dissoc tokens k)))
tokens
tokens)]
(ctob/group-by-type tokens)))
[empty-group filled-group]
(mf/with-memo [tokens-by-type]
(get-sorted-token-groups tokens-by-type))]
token-groups (mf/with-memo [tokens selected-token-set-tokens]
(-> (select-keys tokens (keys selected-token-set-tokens))
(sorted-token-groups)))]
[:*
[:& token-context-menu]
[:& title-bar {:all-clickable true
:title (tr "workspace.token.tokens-section-title" selected-token-set-name)}]
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups)
(:empty token-groups))]
[:& token-component {:key token-key
:type token-key
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:tokens tokens
:token-type-props token-type-props}])]))
(for [type filled-group]
(let [tokens (get tokens-by-type type)]
[:> token-group* {:key (name type)
:is-open (get open-status type false)
:type type
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:tokens tokens}]))
(for [type empty-group]
[:> token-group* {:key (name type)
:type type
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:tokens []}])]))
(mf/defc import-export-button
{::mf/wrap-props false}
@ -357,22 +399,21 @@
:on-click on-export}
(tr "labels.export")]]]))
(mf/defc tokens-sidebar-tab
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[_props]
(mf/defc tokens-sidebar-tab*
{::mf/wrap [mf/memo]}
[]
(let [{on-pointer-down-pages :on-pointer-down
on-lost-pointer-capture-pages :on-lost-pointer-capture
on-pointer-move-pages :on-pointer-move
size-pages-opened :size}
(use-resize-hook :tokens 200 38 "0.6" :y false nil)]
[:div {:class (stl/css :sidebar-wrapper)}
[:& themes-sets-tab {:resize-height size-pages-opened}]
[:> themes-sets-tab* {:resize-height size-pages-opened}]
[:article {:class (stl/css :tokens-section-wrapper)
:data-testid "tokens-sidebar"}
[:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages
:on-lost-pointer-capture on-lost-pointer-capture-pages
:on-pointer-move on-pointer-move-pages}]
[:& tokens-tab]]
[:> tokens-tab*]]
[:& import-export-button]]))

View file

@ -295,6 +295,8 @@
prefer-selected-token-set-tokens (merge active-theme-tokens selected-token-set-tokens)]
(use-resolved-tokens prefer-selected-token-set-tokens)))
(defn use-active-theme-sets-tokens []
(defn use-active-theme-tokens
"A hook that returns active tokens for the current active theme"
[]
(-> (mf/deref refs/workspace-active-theme-sets-tokens)
(use-resolved-tokens {:cache-atom !theme-tokens-cache})))

View file

@ -95,7 +95,6 @@
is-open? (:is-open? state)
;; Dropdown
dropdown-element* (mf/use-ref nil)
on-close-dropdown (mf/use-fn #(swap! state* assoc :is-open? false))
on-open-dropdown
@ -118,8 +117,7 @@
current-label]
[:> icon* {:icon-id i/arrow-down :class (stl/css :dropdown-button) :aria-hidden true}]
[:& dropdown {:show is-open?
:on-close on-close-dropdown
:ref dropdown-element*}
:on-close on-close-dropdown}
[:& theme-options {:active-theme-paths active-theme-paths
:themes themes
:on-close on-close-dropdown}]]]))

View file

@ -1,6 +1,7 @@
(ns app.main.ui.workspace.tokens.token
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
[clojure.set :as set]
[cuerdas.core :as str]))
@ -21,7 +22,9 @@
{:value parsed-value
:unit unit}))))
(defn token-identifier [{:keys [name] :as _token}]
;; FIXME: looks very redundant function
(defn token-identifier
[{:keys [name] :as _token}]
name)
(defn attributes-map
@ -43,7 +46,7 @@
(defn token-attribute-applied?
"Test if `token` is applied to a `shape` on single `token-attribute`."
[token shape token-attribute]
(when-let [id (get-in shape [:applied-tokens token-attribute])]
(when-let [id (dm/get-in shape [:applied-tokens token-attribute])]
(= (token-identifier token) id)))
(defn token-applied?
@ -56,15 +59,18 @@
[token shapes token-attributes]
(some #(token-applied? token % token-attributes) shapes))
(defn shapes-ids-by-applied-attributes [token shapes token-attributes]
(reduce (fn [acc shape]
(let [applied-ids-by-attribute (->> (map #(when (token-attribute-applied? token shape %)
[% #{(:id shape)}])
token-attributes)
(filter some?)
(into {}))]
(merge-with into acc applied-ids-by-attribute)))
{} shapes))
(defn shapes-ids-by-applied-attributes
[token shapes token-attributes]
(let [conj* (fnil conj #{})]
(reduce (fn [result shape]
(let [shape-id (dm/get-prop shape :id)]
(->> token-attributes
(filter #(token-attribute-applied? token shape %))
(reduce (fn [result attr]
(update result attr conj* shape-id))
result))))
{}
shapes)))
(defn shapes-applied-all? [ids-by-attributes shape-ids attributes]
(every? #(set/superset? (get ids-by-attributes %) shape-ids) attributes))
@ -122,6 +128,11 @@
(defn color-token? [token]
(= (:type token) :color))
;; FIXME: this should be precalculated ?
(defn is-reference? [token]
(str/includes? (:value token) "{"))
(defn color-bullet-color [token-color-value]
(when-let [tc (tinycolor/valid-color token-color-value)]
(if (tinycolor/alpha tc)

View file

@ -1,14 +1,21 @@
;; 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.token-pill
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.files.helpers :as cfh]
[app.common.types.tokens-lib :as ctob]
[app.main.refs :as refs]
[app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
[app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon*]]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token :as wtt]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
@ -75,9 +82,8 @@
;; Helper functions
(defn partially-applied-attr
"Translates partially applied attributes based on the dictionary."
[app-token-keys is-applied token-type-props]
(let [{:keys [attributes all-attributes]} token-type-props
filtered-keys (if all-attributes
[app-token-keys is-applied {:keys [attributes all-attributes]}]
(let [filtered-keys (if all-attributes
(filter #(contains? all-attributes %) app-token-keys)
(filter #(contains? attributes %) app-token-keys))]
(when is-applied
@ -94,11 +100,11 @@
(str/join ", " (map attribute-dictionary values)) ".")))
grouped-values)))
(defn token-pill-tooltip
"Generates a tooltip for a given token."
[is-viewer shape token-type-props token half-applied no-valid-value ref-not-in-active-set]
(defn- generate-tooltip
"Generates a tooltip for a given token"
[is-viewer shape token half-applied no-valid-value ref-not-in-active-set]
(let [{:keys [name value resolved-value type]} token
{:keys [title]} token-type-props
{:keys [title] :as token-props} (wtch/get-token-properties token)
applied-tokens (:applied-tokens shape)
app-token-vals (set (vals applied-tokens))
app-token-keys (keys applied-tokens)
@ -106,7 +112,7 @@
applied-to (if half-applied
(partially-applied-attr app-token-keys is-applied? token-type-props)
(partially-applied-attr app-token-keys is-applied? token-props)
(tr "labels.all"))
grouped-values (group-by dimensions-dictionary app-token-keys)
@ -134,54 +140,94 @@
;; Otherwise only show the base title
:else base-title)))
;; FIXME: the token thould already have precalculated references, so
;; we don't need to perform this regex operation on each rerender
(defn contains-reference-value?
"Extracts the value between `{}` in a string and checks if it's in the provided vector."
[text values]
[text active-tokens]
(let [match (second (re-find #"\{([^}]+)\}" text))]
(boolean (some #(= % match) values))))
(contains? active-tokens match)))
(mf/defc token-pill
{::mf/wrap-props false}
[{:keys [on-click token theme-token full-applied on-context-menu half-applied selected-shapes token-type-props active-theme-tokens]}]
(def ^:private
xf:map-id
(map :id))
(defn- applied-all-attributes?
[token selected-shapes attributes]
(let [ids-by-attributes (wtt/shapes-ids-by-applied-attributes token selected-shapes attributes)
shape-ids (into #{} xf:map-id selected-shapes)]
(wtt/shapes-applied-all? ids-by-attributes shape-ids attributes)))
(mf/defc token-pill*
{::mf/wrap [mf/memo]}
[{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}]
(let [{:keys [name value errors]} token
is-reference (some #(= % "{") value)
has-selected? (pos? (count selected-shapes))
is-reference? (wtt/is-reference? token)
contains-path? (str/includes? name ".")
{:keys [attributes all-attributes]}
(get wtch/token-properties (:type token))
full-applied?
(if has-selected?
(applied-all-attributes? token selected-shapes (d/nilv all-attributes attributes))
true)
applied?
(if has-selected?
(wtt/shapes-token-applied? token selected-shapes (d/nilv all-attributes attributes))
false)
half-applied?
(and applied? (not full-applied?))
;; FIXME: move to context or props
can-edit? (:can-edit (deref refs/permissions))
is-viewer (not can-edit?)
ref-not-in-active-set (and is-reference
(not (contains-reference-value? value (keys active-theme-tokens))))
is-viewer? (not can-edit?)
ref-not-in-active-set
(and is-reference?
(not (contains-reference-value? value active-theme-tokens)))
no-valid-value (seq errors)
errors? (or ref-not-in-active-set
no-valid-value)
color (when (seq (ctob/find-token-value-references value))
(wtt/resolved-token-bullet-color theme-token))
contains-path? (str/includes? name ".")
splitted-name (cfh/split-by-last-period name)
color (or color (wtt/resolved-token-bullet-color token))
errors?
(or ref-not-in-active-set
no-valid-value)
color
(when (wtt/color-token? token)
(let [theme-token (get active-theme-tokens (:name token))]
(or (wtt/resolved-token-bullet-color theme-token)
(wtt/resolved-token-bullet-color token))))
on-click
(mf/use-callback
(mf/deps errors? on-click)
(mf/use-fn
(mf/deps errors? on-click token)
(fn [event]
(dom/stop-propagation event)
(when (and (not (seq errors)) on-click)
(on-click event))))
(on-click event token))))
token-status-id (cond
half-applied
"token-status-partial"
full-applied
"token-status-full"
:else
"token-status-non-applied")
token-status-id
(cond
half-applied?
"token-status-partial"
full-applied?
"token-status-full"
:else
"token-status-non-applied")
on-context-menu
(mf/use-fn
(mf/deps can-edit? on-context-menu)
(mf/deps can-edit? on-context-menu token)
(fn [e]
(dom/stop-propagation e)
(when can-edit?
(on-context-menu e))))
(on-context-menu e token))))
on-click
(mf/use-fn
@ -191,26 +237,29 @@
(when (and can-edit? (not (seq errors)) on-click)
(on-click event))))
;; FIXME: missing deps
on-hover
(mf/use-fn
(mf/deps selected-shapes is-viewer)
(mf/deps selected-shapes is-viewer?)
(fn [event]
(let [node (dom/get-current-target event)
title (token-pill-tooltip is-viewer (first selected-shapes) token-type-props token half-applied no-valid-value ref-not-in-active-set)]
(let [node (dom/get-current-target event)
title (generate-tooltip is-viewer? (first selected-shapes) token
half-applied? no-valid-value ref-not-in-active-set)]
(dom/set-attribute! node "title" title))))]
[:button {:class (stl/css-case :token-pill true
:token-pill-default can-edit?
:token-pill-applied (and can-edit? (or half-applied full-applied))
:token-pill-invalid (and can-edit? errors?)
:token-pill-invalid-applied (and full-applied errors? can-edit?)
:token-pill-viewer is-viewer
:token-pill-applied-viewer (and is-viewer
(or half-applied full-applied))
:token-pill-invalid-viewer (and is-viewer
errors?)
:token-pill-invalid-applied-viewer (and is-viewer
(and full-applied errors?)))
[:button {:class (stl/css-case
:token-pill true
:token-pill-default can-edit?
:token-pill-applied (and can-edit? has-selected? (or half-applied? full-applied?))
:token-pill-invalid (and can-edit? errors?)
:token-pill-invalid-applied (and full-applied? errors? can-edit?)
:token-pill-viewer is-viewer?
:token-pill-applied-viewer (and is-viewer? has-selected?
(or half-applied? full-applied?))
:token-pill-invalid-viewer (and is-viewer?
errors?)
:token-pill-invalid-applied-viewer (and is-viewer?
(and full-applied? errors?)))
:type "button"
:on-click on-click
:on-mouse-enter on-hover
@ -220,6 +269,7 @@
[:> icon*
{:icon-id "broken-link"
:class (stl/css :token-pill-icon)}]
color
[:& color-bullet {:color color
:mini true}]
@ -227,13 +277,13 @@
[:> token-status-icon*
{:icon-id token-status-id
:class (stl/css :token-pill-icon)}])
(if contains-path?
[:span {:class (stl/css :divided-name-wrapper)
:aria-label name}
[:span {:class (stl/css :first-name-wrapper)}
(first splitted-name)]
[:span {:class (stl/css :last-name-wrapper)}
(last splitted-name)]]
(let [[first-part last-part] (cfh/split-by-last-period name)]
[:span {:class (stl/css :divided-name-wrapper)
:aria-label name}
[:span {:class (stl/css :first-name-wrapper)} first-part]
[:span {:class (stl/css :last-name-wrapper)} last-part]])
[:span {:class (stl/css :name-wrapper)
:aria-label name}
name])]))
name])]))

View file

@ -1,89 +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.token-types
(:require
[app.common.data :as d :refer [ordered-map]]
[app.common.types.token :as ctt]
[app.main.ui.workspace.tokens.changes :as wtch]
[clojure.set :as set]))
(def token-types
(ordered-map
:border-radius
{:title "Border Radius"
:attributes ctt/border-radius-keys
:on-update-shape wtch/update-shape-radius-all
:modal {:key :tokens/border-radius
:fields [{:label "Border Radius"
:key :border-radius}]}}
:color
{:title "Color"
:attributes #{:fill}
:all-attributes ctt/color-keys
:on-update-shape wtch/update-fill-stroke
:modal {:key :tokens/color
:fields [{:label "Color" :key :color}]}}
:stroke-width
{:title "Stroke Width"
:attributes ctt/stroke-width-keys
:on-update-shape wtch/update-stroke-width
:modal {:key :tokens/stroke-width
:fields [{:label "Stroke Width"
:key :stroke-width}]}}
:sizing
{:title "Sizing"
:attributes #{:width :height}
:all-attributes ctt/sizing-keys
:on-update-shape wtch/update-shape-dimensions
:modal {:key :tokens/sizing
:fields [{:label "Sizing"
:key :sizing}]}}
:dimensions
{:title "Dimensions"
:attributes #{:width :height}
:all-attributes (set/union
ctt/spacing-keys
ctt/sizing-keys
ctt/border-radius-keys
ctt/stroke-width-keys)
:on-update-shape wtch/update-shape-dimensions
:modal {:key :tokens/dimensions
:fields [{:label "Dimensions"
:key :dimensions}]}}
:opacity
{:title "Opacity"
:attributes ctt/opacity-keys
:on-update-shape wtch/update-opacity
:modal {:key :tokens/opacity
:fields [{:label "Opacity"
:key :opacity}]}}
:rotation
{:title "Rotation"
:attributes ctt/rotation-keys
:on-update-shape wtch/update-rotation
:modal {:key :tokens/rotation
:fields [{:label "Rotation"
:key :rotation}]}}
:spacing
{:title "Spacing"
:attributes #{:column-gap :row-gap}
:all-attributes ctt/spacing-keys
:on-update-shape wtch/update-layout-spacing
:modal {:key :tokens/spacing
:fields [{:label "Spacing"
:key :spacing}]}}))
(defn get-token-properties [token]
(get token-types (:type token)))
(defn token-attributes [token-type]
(get-in token-types [token-type :attributes]))

View file

@ -6,9 +6,10 @@
(ns app.util.array
"A collection of helpers for work with javascript arrays."
(:refer-clojure :exclude [conj! conj filter map reduce find])
(:refer-clojure :exclude [conj! conj filter map reduce find sort])
(:require
[cljs.core :as c]))
[cljs.core :as c]
[goog.array :as garray]))
(defn conj
"A conj like function for js arrays."
@ -67,3 +68,9 @@
(defn find
[f v]
(.find ^js/Array v f))
(defn sort!
[a]
(garray/sort a compare)
a)