Files
penpot/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs
Florian Schroedl 8f852bf48f Use :as
2024-05-15 17:26:10 +02:00

156 lines
6.9 KiB
Clojure

;; 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.sidebar
(:require-macros [app.main.style :as stl])
(:require
[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.icons :as i]
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.tokens.common :refer [workspace-shapes]]
[app.main.ui.workspace.tokens.core :as wtc]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(mf/defc token-pill
{::mf/wrap-props false}
[{:keys [on-click token highlighted? on-context-menu]}]
(let [{:keys [name value]} token
resolved-value (try
(wtc/resolve-token-value token)
(catch js/Error _ nil))]
[:div {:class (stl/css-case :token-pill true
:token-pill-highlighted highlighted?
:token-pill-invalid (not resolved-value))
:title (str (if resolved-value "Token value: " "Invalid token value: ") value)
:on-click on-click
:on-context-menu on-context-menu}
name]))
(mf/defc token-section-icon
{::mf/wrap-props false}
[{:keys [type]}]
(case type
:border-radius i/corner-radius
:numeric [:span {:class (stl/css :section-text-icon)} "123"]
:boolean i/boolean-difference
:opacity [:span {:class (stl/css :section-text-icon)} "%"]
:rotation i/rotation
:spacing i/padding-extended
:string i/text-mixed
:stroke-width i/stroke-size
:typography i/text
;; TODO: Add diagonal icon here when it's available
:dimension [:div {:style {:rotate "45deg"}} i/constraint-horizontal]
:sizing [:div {:style {:rotate "45deg"}} i/constraint-horizontal]
i/add))
(mf/defc token-component
[{:keys [type file tokens selected-shapes token-type-props]}]
(let [open? (mf/use-state false)
{:keys [modal attributes title]} token-type-props
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)
:token-id (:id token)}))))
on-toggle-open-click (mf/use-fn
(mf/deps open? tokens)
#(when (seq tokens)
(swap! open? not)))
on-popover-open-click (mf/use-fn
(fn [event]
(let [{:keys [key fields]} modal]
(dom/stop-propagation event)
(modal/show! key {:x (.-clientX ^js event)
:y (.-clientY ^js event)
:position :right
:fields fields
:token-type type}))))
on-token-pill-click (mf/use-fn
(mf/deps selected-shapes token-type-props)
(fn [event token]
(dom/stop-propagation event)
(wtc/on-apply-token {:token token
:token-type-props token-type-props
:selected-shapes selected-shapes})))
tokens-count (count tokens)]
[:div {:on-click on-toggle-open-click}
[:& cmm/asset-section {:file-id (:id file)
:icon (mf/fnc icon-wrapper [_]
[:div {:class (stl/css :section-icon)}
[:& token-section-icon {:type type}]])
:title title
:assets-count tokens-count
:open? @open?}
[:& cmm/asset-section-block {:role :title-button}
[:button {:class (stl/css :action-button)
:on-click on-popover-open-click}
i/add]]
(when open?
[:& cmm/asset-section-block {:role :content}
[:div {:class (stl/css :token-pills-wrapper)}
(for [token tokens]
[:& token-pill
{:key (:id token)
:token token
:highlighted? (wtc/tokens-applied? token selected-shapes attributes)
:on-click #(on-token-pill-click % token)
:on-context-menu #(on-context-menu % token)}])]])]]))
(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-group (->> (vals tokens)
(group-by :type))
{:keys [empty filled]} (->> wtc/token-types
(map (fn [[token-key token-type-props]]
{:token-key token-key
:token-type-props token-type-props
:tokens (get tokens-by-group token-key [])}))
(group-by (fn [{:keys [tokens]}]
(if (empty? tokens) :empty :filled))))]
{:empty (sort-by :token-key empty)
:filled (sort-by :token-key filled)}))
(mf/defc tokens-explorer
[_props]
(let [file (mf/deref refs/workspace-file)
current-page-id (:current-page-id @st/state)
workspace-data (mf/deref refs/workspace-data)
tokens (get workspace-data :tokens)
token-groups (mf/with-memo [tokens]
(sorted-token-groups tokens))
selected-shape-ids (mf/deref refs/selected-shapes)
selected-shapes (workspace-shapes workspace-data current-page-id selected-shape-ids)]
[:article
[:div.assets-bar
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups)
(:empty token-groups))]
[:& token-component {:key token-key
:type token-key
:file file
:selected-shapes selected-shapes
:tokens tokens
:token-type-props token-type-props}])]]))
(mf/defc tokens-sidebar-tab
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[_props]
[:div {:class (stl/css :sidebar-tab-wrapper)}
[:& tokens-explorer]])