mirror of
https://github.com/penpot/penpot.git
synced 2025-01-06 14:50:20 -05:00
Merge pull request #264 from tokens-studio/token-sets-themes-ui
Token sets themes UI
This commit is contained in:
commit
27409f43d2
15 changed files with 1072 additions and 121 deletions
|
@ -102,6 +102,17 @@
|
|||
(rx/of
|
||||
(dch/commit-changes changes)))))))
|
||||
|
||||
(defn update-token-theme [token-theme]
|
||||
(ptk/reify ::update-token-theme
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [prev-token-theme (wtts/get-workspace-token-theme state (:id token-theme))
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/update-token-theme token-theme prev-token-theme))]
|
||||
(js/console.log "changes" changes)
|
||||
(rx/of
|
||||
(dch/commit-changes changes))))))
|
||||
|
||||
(defn ensure-token-theme-changes [changes state {:keys [id new-set?]}]
|
||||
(let [theme-id (wtts/update-theme-id state)
|
||||
theme (some-> theme-id (wtts/get-workspace-token-theme state))]
|
||||
|
@ -155,19 +166,32 @@
|
|||
(ensure-token-theme-changes state {:id (:id new-token-set)
|
||||
:new-set? true}))]
|
||||
(rx/of
|
||||
(set-selected-token-set-id (:id new-token-set))
|
||||
(dch/commit-changes changes)))))))
|
||||
|
||||
(defn toggle-token-set [token-set-id]
|
||||
(defn update-token-set [token-set]
|
||||
(ptk/reify ::update-token-set
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [prev-token-set (wtts/get-token-set (:id token-set) state)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/update-token-set token-set prev-token-set))]
|
||||
(rx/of
|
||||
(dch/commit-changes changes))))))
|
||||
|
||||
(defn toggle-token-set [{:keys [token-set-id]}]
|
||||
(ptk/reify ::toggle-token-set
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [theme (some-> (wtts/update-theme-id state)
|
||||
(wtts/get-workspace-token-theme state))
|
||||
(let [target-theme-id (wtts/get-temp-theme-id state)
|
||||
active-set-ids (wtts/get-active-set-ids state)
|
||||
theme (-> (wtts/get-workspace-token-theme target-theme-id state)
|
||||
(assoc :sets active-set-ids))
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/update-token-theme
|
||||
(wtts/toggle-token-set-to-token-theme token-set-id theme)
|
||||
theme)
|
||||
(pcb/update-active-token-themes #{(wtts/update-theme-id state)} (wtts/get-active-theme-ids state)))]
|
||||
(pcb/update-active-token-themes #{target-theme-id} (wtts/get-active-theme-ids state)))]
|
||||
(rx/of
|
||||
(dch/commit-changes changes)
|
||||
(wtu/update-workspace-tokens))))))
|
||||
|
|
|
@ -240,9 +240,16 @@
|
|||
st/state
|
||||
=))
|
||||
|
||||
(defn workspace-token-theme
|
||||
[id]
|
||||
(l/derived #(wtts/get-workspace-theme id %) st/state))
|
||||
|
||||
(def workspace-active-theme-ids
|
||||
(l/derived wtts/get-active-theme-ids st/state))
|
||||
|
||||
(def workspace-temp-theme-id
|
||||
(l/derived wtts/get-temp-theme-id st/state))
|
||||
|
||||
(def workspace-active-set-ids
|
||||
(l/derived wtts/get-active-set-ids st/state))
|
||||
|
||||
|
@ -252,16 +259,19 @@
|
|||
(def workspace-ordered-token-themes
|
||||
(l/derived wtts/get-workspace-ordered-themes st/state))
|
||||
|
||||
(def workspace-token-sets
|
||||
(def workspace-ordered-token-sets
|
||||
(l/derived
|
||||
(fn [data]
|
||||
(or (wtts/get-workspace-sets data) {}))
|
||||
(or (wtts/get-workspace-ordered-sets data) {}))
|
||||
st/state
|
||||
=))
|
||||
|
||||
(def workspace-active-theme-sets-tokens
|
||||
(l/derived wtts/get-active-theme-sets-tokens-names-map st/state =))
|
||||
|
||||
(def workspace-ordered-token-sets-tokens
|
||||
(l/derived wtts/get-workspace-ordered-sets-tokens st/state =))
|
||||
|
||||
(def workspace-selected-token-set-tokens
|
||||
(l/derived
|
||||
(fn [data]
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui
|
||||
(:require
|
||||
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
|
||||
[app.config :as cf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
|
|
@ -144,7 +144,7 @@ Token names should only contain letters and digits separated by . characters.")}
|
|||
(mf/defc form
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [token token-type] :as _args}]
|
||||
(let [selected-set-tokens (mf/deref refs/workspace-selected-token-set-tokens)
|
||||
(let [tokens (mf/deref refs/workspace-ordered-token-sets-tokens)
|
||||
active-theme-tokens (mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
resolved-tokens (sd/use-resolved-tokens active-theme-tokens {:names-map? true
|
||||
:cache-atom form-token-cache-atom})
|
||||
|
@ -152,9 +152,9 @@ Token names should only contain letters and digits separated by . characters.")}
|
|||
(mf/deps (:name token))
|
||||
#(wtt/token-name->path (:name token)))
|
||||
selected-set-tokens-tree (mf/use-memo
|
||||
(mf/deps token-path selected-set-tokens)
|
||||
(mf/deps token-path tokens)
|
||||
(fn []
|
||||
(-> (wtt/token-names-tree selected-set-tokens)
|
||||
(-> (wtt/token-names-tree tokens)
|
||||
;; Allow setting editing token to it's own path
|
||||
(d/dissoc-in token-path))))
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.workspace.tokens.form :refer [form]]
|
||||
[okulary.core :as l]
|
||||
|
@ -37,7 +38,7 @@
|
|||
(-> (calculate-position vport position x y)
|
||||
(clj->js))))
|
||||
|
||||
(mf/defc modal
|
||||
(mf/defc token-update-create-modal
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [x y position token token-type] :as _args}]
|
||||
(let [wrapper-style (use-viewport-position-style x y position)]
|
||||
|
@ -47,82 +48,88 @@
|
|||
[:& form {:token token
|
||||
:token-type token-type}]]))
|
||||
|
||||
(mf/defc token-themes-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/themes}
|
||||
[args]
|
||||
[:& wtmt/modal args])
|
||||
|
||||
;; Modals ----------------------------------------------------------------------
|
||||
|
||||
(mf/defc boolean-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/boolean}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc border-radius-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/border-radius}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc stroke-width-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/stroke-width}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc box-shadow-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/box-shadow}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc sizing-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/sizing}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc dimensions-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/dimensions}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc numeric-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/numeric}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc opacity-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/opacity}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc other-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/other}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc rotation-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/rotation}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc spacing-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/spacing}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc string-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/string}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
||||
(mf/defc typography-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :tokens/typography}
|
||||
[properties]
|
||||
[:& modal properties])
|
||||
[:& token-update-create-modal properties])
|
||||
|
|
237
frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs
Normal file
237
frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs
Normal file
|
@ -0,0 +1,237 @@
|
|||
;; 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.modals.themes
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.tokens :as wdt]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.tokens.common :refer [labeled-input]]
|
||||
[app.main.ui.workspace.tokens.sets :as wts]
|
||||
[app.main.ui.workspace.tokens.token-set :as wtts]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[app.main.ui.workspace.tokens.sets-context :as sets-context]))
|
||||
|
||||
(def ^:private chevron-icon
|
||||
(i/icon-xref :arrow (stl/css :chevron-icon)))
|
||||
|
||||
(def ^:private close-icon
|
||||
(i/icon-xref :close (stl/css :close-icon)))
|
||||
|
||||
(mf/defc empty-themes
|
||||
[{:keys [set-state]}]
|
||||
[:div {:class (stl/css :empty-themes-wrapper)}
|
||||
[:div {:class (stl/css :empty-themes-message)}
|
||||
[:h1 "You currently have no themes."]
|
||||
[:p "Create your first theme now."]]
|
||||
[:div {:class (stl/css :button-footer)}
|
||||
[:button {:class (stl/css :button-primary)
|
||||
:on-click #(set-state (fn [_] {:type :create-theme}))}
|
||||
"New theme"]]])
|
||||
|
||||
(mf/defc switch
|
||||
[{:keys [selected? name on-change]}]
|
||||
(let [selected (if selected? :on :off)]
|
||||
[:& radio-buttons {:selected selected
|
||||
:on-change on-change
|
||||
:name name}
|
||||
[:& radio-button {:id :on
|
||||
:value :on
|
||||
:icon i/tick
|
||||
:label ""}]
|
||||
[:& radio-button {:id :off
|
||||
:value :off
|
||||
:icon i/close
|
||||
:label ""}]]))
|
||||
|
||||
(mf/defc themes-overview
|
||||
[{:keys [set-state]}]
|
||||
(let [active-theme-ids (mf/deref refs/workspace-active-theme-ids)
|
||||
themes (mf/deref refs/workspace-ordered-token-themes)
|
||||
on-edit-theme (fn [theme e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(set-state (fn [_] {:type :edit-theme
|
||||
:theme-id (:id theme)})))]
|
||||
[:div
|
||||
[:ul {:class (stl/css :theme-group-wrapper)}
|
||||
(for [[group themes] themes]
|
||||
[:li {:key (str "token-theme-group" group)}
|
||||
(when (seq group)
|
||||
[:span {:class (stl/css :theme-group-label)} group])
|
||||
[:ul {:class (stl/css :theme-group-rows-wrapper)}
|
||||
(for [{:keys [id name] :as theme} themes
|
||||
:let [selected? (some? (get active-theme-ids id))]]
|
||||
[:li {:key (str "token-theme-" id)
|
||||
:class (stl/css :theme-row)}
|
||||
[:div {:class (stl/css :theme-row-left)}
|
||||
[:div {:on-click (fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (wdt/toggle-token-theme id)))}
|
||||
[:& switch {:name (str "Theme" name)
|
||||
:on-change (constantly nil)
|
||||
:selected? selected?}]]
|
||||
[:span {:class (stl/css :theme-row-label)} name]]
|
||||
[:div {:class (stl/css :theme-row-right)}
|
||||
(if-let [sets-count (some-> theme :sets seq count)]
|
||||
[:button {:class (stl/css :sets-count-button)
|
||||
:on-click #(on-edit-theme theme %)}
|
||||
(str sets-count " sets")
|
||||
chevron-icon]
|
||||
[:button {:class (stl/css :sets-count-empty-button)
|
||||
:on-click #(on-edit-theme theme %)}
|
||||
"No sets defined"
|
||||
chevron-icon])
|
||||
[:div {:class (stl/css :delete-theme-button)}
|
||||
[:button {:on-click (fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (wdt/delete-token-theme id)))}
|
||||
i/delete]]]])]])]
|
||||
[:div {:class (stl/css :button-footer)}
|
||||
[:button {:class (stl/css :create-theme-button)
|
||||
:on-click (fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(set-state (fn [_] {:type :create-theme})))}
|
||||
i/add
|
||||
"Create theme"]]]))
|
||||
|
||||
(mf/defc edit-theme
|
||||
[{:keys [token-sets theme on-back on-submit] :as props}]
|
||||
(let [edit? (some? (:id theme))
|
||||
theme-state (mf/use-state {:token-sets token-sets
|
||||
:theme theme})
|
||||
disabled? (-> (get-in @theme-state [:theme :name])
|
||||
(str/trim)
|
||||
(str/empty?))
|
||||
token-set-active? (mf/use-callback
|
||||
(mf/deps theme-state)
|
||||
(fn [id]
|
||||
(get-in @theme-state [:theme :sets id])))
|
||||
on-toggle-token-set (mf/use-callback
|
||||
(mf/deps theme-state)
|
||||
(fn [token-set-id]
|
||||
(swap! theme-state (fn [st]
|
||||
(update st :theme #(wtts/toggle-token-set-to-token-theme token-set-id %))))))
|
||||
on-change-field (fn [field]
|
||||
(fn [e]
|
||||
(swap! theme-state (fn [st] (assoc-in st field (dom/get-target-val e))))))
|
||||
on-update-group (on-change-field [:theme :group])
|
||||
on-update-name (on-change-field [:theme :name])
|
||||
on-save-form (mf/use-callback
|
||||
(mf/deps theme-state on-submit)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(let [theme (:theme @theme-state)
|
||||
final-name (str/trim (:name theme))
|
||||
final-group (-> (:group theme)
|
||||
(str/trim)
|
||||
(str/lower))]
|
||||
(when-not (str/empty? final-name)
|
||||
(cond-> theme
|
||||
(empty final-group) (dissoc :group)
|
||||
:always on-submit)))
|
||||
(on-back)))]
|
||||
[:form {:on-submit on-save-form}
|
||||
[:div {:class (stl/css :edit-theme-wrapper)}
|
||||
[:div
|
||||
[:button {:class (stl/css :back-button)
|
||||
:type "button"
|
||||
:on-click on-back}
|
||||
chevron-icon "Back"]]
|
||||
[:div {:class (stl/css :edit-theme-inputs-wrapper)}
|
||||
[:& labeled-input {:label "Group"
|
||||
:input-props {:default-value (:group theme)
|
||||
:on-change on-update-group}}]
|
||||
[:& labeled-input {:label "Theme"
|
||||
:input-props {:default-value (:name theme)
|
||||
:on-change on-update-name}}]]
|
||||
[:div {:class (stl/css :sets-list-wrapper)}
|
||||
[:& wts/controlled-sets-list
|
||||
{:token-sets token-sets
|
||||
:token-set-selected? (constantly false)
|
||||
:token-set-active? token-set-active?
|
||||
:on-select on-toggle-token-set
|
||||
:on-toggle-token-set on-toggle-token-set
|
||||
:context sets-context/static-context}]]
|
||||
[:div {:class (stl/css :edit-theme-footer)}
|
||||
(if edit?
|
||||
[:button {:class (stl/css :button-secondary)
|
||||
:type "button"
|
||||
:on-click (fn []
|
||||
(st/emit! (wdt/delete-token-theme (:id theme)))
|
||||
(on-back))}
|
||||
"Delete"]
|
||||
[:div])
|
||||
[:div {:class (stl/css :button-footer)}
|
||||
[:button {:class (stl/css :button-secondary)
|
||||
:type "button"
|
||||
:on-click #(st/emit! (modal/hide))}
|
||||
"Cancel"]
|
||||
[:button {:class (stl/css :button-primary)
|
||||
:type "submit"
|
||||
:on-click on-save-form
|
||||
:disabled disabled?}
|
||||
"Save theme"]]]]]))
|
||||
|
||||
(mf/defc controlled-edit-theme
|
||||
[{:keys [state set-state]}]
|
||||
(let [{:keys [theme-id]} @state
|
||||
token-sets (mf/deref refs/workspace-ordered-token-sets)
|
||||
theme (mf/deref (refs/workspace-token-theme theme-id))]
|
||||
[:& edit-theme
|
||||
{:token-sets token-sets
|
||||
:theme theme
|
||||
:on-back #(set-state (constantly {:type :themes-overview}))
|
||||
:on-submit #(st/emit! (wdt/update-token-theme %))}]))
|
||||
|
||||
(mf/defc create-theme
|
||||
[{:keys [set-state]}]
|
||||
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
|
||||
theme {:name "" :sets #{}}]
|
||||
[:& edit-theme
|
||||
{:token-sets token-sets
|
||||
:theme theme
|
||||
:on-back #(set-state (constantly {:type :themes-overview}))
|
||||
:on-submit #(st/emit! (wdt/create-token-theme %))}]))
|
||||
|
||||
(mf/defc themes
|
||||
[{:keys [] :as _args}]
|
||||
(let [themes (mf/deref refs/workspace-ordered-token-themes)
|
||||
state (mf/use-state (if (empty? themes)
|
||||
{:type :create-theme}
|
||||
{:type :themes-overview}))
|
||||
set-state (mf/use-callback #(swap! state %))
|
||||
title (case (:type @state)
|
||||
:edit-theme "Edit Theme"
|
||||
"Themes")
|
||||
component (case (:type @state)
|
||||
:empty-themes empty-themes
|
||||
:themes-overview (if (empty? themes) empty-themes themes-overview)
|
||||
:edit-theme controlled-edit-theme
|
||||
:create-theme create-theme)]
|
||||
[:div
|
||||
[:div {:class (stl/css :modal-title)} title]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:& component {:state state
|
||||
:set-state set-state}]]]))
|
||||
|
||||
(mf/defc modal
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [] :as _args}]
|
||||
(let [handle-close-dialog (mf/use-callback #(st/emit! (modal/hide)))]
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-dialog)}
|
||||
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
|
||||
[:& themes]]]))
|
213
frontend/src/app/main/ui/workspace/tokens/modals/themes.scss
Normal file
213
frontend/src/app/main/ui/workspace/tokens/modals/themes.scss
Normal file
|
@ -0,0 +1,213 @@
|
|||
// 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";
|
||||
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: var(--color-background-tertiary);
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
@extend .modal-container-base;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
width: 100%;
|
||||
max-width: $s-468;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@include headlineMediumTypography;
|
||||
font-weight: 500;
|
||||
margin-block-end: $s-16;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: $s-6;
|
||||
}
|
||||
|
||||
.edit-theme-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
@extend .button-primary;
|
||||
padding: $s-6;
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
@extend .button-secondary;
|
||||
padding: $s-6;
|
||||
}
|
||||
|
||||
.empty-themes-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.empty-themes-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: $s-12;
|
||||
padding: $s-72 0;
|
||||
|
||||
h1 {
|
||||
@include headlineLargeTypography;
|
||||
}
|
||||
|
||||
p {
|
||||
@include bodyMediumTypography;
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.create-theme-button {
|
||||
@extend .button-secondary;
|
||||
padding: $s-6;
|
||||
padding-right: $s-8;
|
||||
svg {
|
||||
margin-right: $s-6;
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
@extend .modal-close-btn-base;
|
||||
}
|
||||
|
||||
.theme-group-label {
|
||||
display: block;
|
||||
@include headlineMediumTypography;
|
||||
color: var(--color-foreground-secondary);
|
||||
margin-bottom: $s-8;
|
||||
}
|
||||
|
||||
.theme-group-rows-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-6;
|
||||
}
|
||||
|
||||
.theme-group-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-8;
|
||||
}
|
||||
|
||||
.theme-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $s-12;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.theme-row-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $s-16;
|
||||
}
|
||||
|
||||
.theme-row-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $s-6;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
@extend .button-tertiary;
|
||||
padding: $s-6;
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
svg {
|
||||
scale: -1 1;
|
||||
margin-left: 0;
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.sets-count-button {
|
||||
@extend .button-secondary;
|
||||
padding: $s-6;
|
||||
padding-left: $s-12;
|
||||
svg {
|
||||
margin-left: $s-6;
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-theme-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-12;
|
||||
}
|
||||
|
||||
.edit-theme-inputs-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 0.6fr 1fr;
|
||||
gap: $s-12;
|
||||
}
|
||||
|
||||
.sets-list-wrapper {
|
||||
border: 1px solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent);
|
||||
border-radius: $s-8;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sets-count-empty-button {
|
||||
@extend .button-secondary;
|
||||
padding: $s-6;
|
||||
padding-left: $s-12;
|
||||
svg {
|
||||
margin-left: $s-6;
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-row-label {
|
||||
@include bodyMediumTypography;
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.delete-theme-button {
|
||||
@extend .button-tertiary;
|
||||
height: $s-28;
|
||||
width: $s-28;
|
||||
button {
|
||||
@include buttonStyle;
|
||||
@include flexCenter;
|
||||
width: $s-24;
|
||||
height: 100%;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
height: $s-12;
|
||||
width: $s-12;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,32 +12,81 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
[app.util.keyboard :as kbd]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]
|
||||
[app.main.ui.workspace.tokens.sets-context :as sets-context]))
|
||||
|
||||
(def ^:private chevron-icon
|
||||
(i/icon-xref :arrow (stl/css :chevron-icon)))
|
||||
|
||||
(defn on-toggle-token-set-click [id event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (wdt/toggle-token-set id)))
|
||||
(defn on-toggle-token-set-click [token-set-id]
|
||||
(st/emit! (wdt/toggle-token-set {:token-set-id token-set-id})))
|
||||
|
||||
(defn on-select-token-set-click [id event]
|
||||
(defn on-select-token-set-click [id]
|
||||
(st/emit! (wdt/set-selected-token-set-id id)))
|
||||
|
||||
(defn on-delete-token-set-click [id event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (wdt/delete-token-set id)))
|
||||
|
||||
(defn on-update-token-set [token-set]
|
||||
(st/emit! (wdt/update-token-set token-set)))
|
||||
|
||||
(defn on-create-token-set [token-set]
|
||||
(st/emit! (wdt/create-token-set token-set)))
|
||||
|
||||
(mf/defc editing-node
|
||||
[{:keys [default-value on-cancel on-submit]}]
|
||||
(let [ref (mf/use-ref)
|
||||
on-submit-valid (mf/use-fn
|
||||
(fn [event]
|
||||
(let [value (str/trim (dom/get-target-val event))]
|
||||
(if (or (str/empty? value)
|
||||
(= value default-value))
|
||||
(on-cancel)
|
||||
(on-submit value)))))
|
||||
on-key-down (mf/use-fn
|
||||
(fn [event]
|
||||
(cond
|
||||
(kbd/enter? event) (on-submit-valid event)
|
||||
(kbd/esc? event) (on-cancel))))]
|
||||
[:input
|
||||
{:class (stl/css :editing-node)
|
||||
:type "text"
|
||||
:ref ref
|
||||
:on-blur on-submit-valid
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
:default-value default-value}]))
|
||||
|
||||
(mf/defc sets-tree
|
||||
[{:keys [token-set token-set-active? token-set-selected?] :as _props}]
|
||||
[{:keys [token-set
|
||||
token-set-active?
|
||||
token-set-selected?
|
||||
editing?
|
||||
on-select
|
||||
on-toggle
|
||||
on-edit
|
||||
on-submit
|
||||
on-cancel]
|
||||
:as _props}]
|
||||
(let [{:keys [id name _children]} token-set
|
||||
selected? (and set? (token-set-selected? id))
|
||||
visible? (token-set-active? id)
|
||||
collapsed? (mf/use-state false)
|
||||
set? true #_(= type :set)
|
||||
group? false #_(= type :group)]
|
||||
group? false #_(= type :group)
|
||||
editing-node? (editing? id)
|
||||
on-select (mf/use-callback
|
||||
(mf/deps editing-node?)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(when-not editing-node?
|
||||
(on-select id))))]
|
||||
[:div {:class (stl/css :set-item-container)
|
||||
:on-click #(on-select-token-set-click id %)}
|
||||
:on-click on-select
|
||||
:on-double-click #(on-edit id)}
|
||||
[:div {:class (stl/css-case :set-item-group group?
|
||||
:set-item-set set?
|
||||
:selected-set selected?)}
|
||||
|
@ -48,25 +97,72 @@
|
|||
chevron-icon])
|
||||
[:span {:class (stl/css :icon)}
|
||||
(if set? i/document i/group)]
|
||||
[:div {:class (stl/css :set-name)} name]
|
||||
[:div {:class (stl/css :delete-set)}
|
||||
[:button {:on-click #(on-delete-token-set-click id %)}
|
||||
i/delete]]
|
||||
(when set?
|
||||
[:span {:class (stl/css :action-btn)
|
||||
:on-click #(on-toggle-token-set-click id %)}
|
||||
(if visible? i/shown i/hide)])]
|
||||
#_(when (and children (not @collapsed?))
|
||||
[:div {:class (stl/css :set-children)}
|
||||
(for [child-id children]
|
||||
[:& sets-tree (assoc props :key child-id
|
||||
{:key child-id}
|
||||
:set-id child-id
|
||||
:selected-set-id selected-token-set-id)])])]))
|
||||
(if editing-node?
|
||||
[:& editing-node {:default-value name
|
||||
:on-submit #(on-submit (assoc token-set :name %))
|
||||
:on-cancel on-cancel}]
|
||||
[:*
|
||||
[:div {:class (stl/css :set-name)} name]
|
||||
[:div {:class (stl/css :delete-set)}
|
||||
[:button {:on-click #(on-delete-token-set-click id %)
|
||||
:type "button"}
|
||||
i/delete]]
|
||||
(if set?
|
||||
[:span {:class (stl/css :action-btn)
|
||||
:on-click (fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(on-toggle id))}
|
||||
(if visible? i/shown i/hide)]
|
||||
nil
|
||||
#_(when (and children (not @collapsed?))
|
||||
[:div {:class (stl/css :set-children)}
|
||||
(for [child-id children]
|
||||
[:& sets-tree (assoc props :key child-id
|
||||
{:key child-id}
|
||||
:set-id child-id
|
||||
:selected-set-id selected-token-set-id)])]))])]]))
|
||||
|
||||
(mf/defc controlled-sets-list
|
||||
[{:keys [token-sets
|
||||
on-update-token-set
|
||||
token-set-selected?
|
||||
token-set-active?
|
||||
on-create-token-set
|
||||
on-toggle-token-set
|
||||
on-select
|
||||
context]
|
||||
:as _props}]
|
||||
(let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context))]
|
||||
[:ul {:class (stl/css :sets-list)}
|
||||
(for [[id token-set] token-sets]
|
||||
(when token-set
|
||||
[:& sets-tree {:key id
|
||||
:token-set token-set
|
||||
:token-set-selected? (if new? (constantly false) token-set-selected?)
|
||||
:token-set-active? token-set-active?
|
||||
:editing? editing?
|
||||
:on-select on-select
|
||||
:on-edit on-edit
|
||||
:on-toggle on-toggle-token-set
|
||||
:on-submit #(do
|
||||
(on-update-token-set %)
|
||||
(on-reset))
|
||||
:on-cancel on-reset}]))
|
||||
(when new?
|
||||
[:& sets-tree {:token-set {:name ""}
|
||||
:token-set-selected? (constantly true)
|
||||
:token-set-active? (constantly true)
|
||||
:editing? (constantly true)
|
||||
:on-select (constantly nil)
|
||||
:on-edit on-create
|
||||
:on-submit #(do
|
||||
(on-create-token-set %)
|
||||
(on-reset))
|
||||
:on-cancel on-reset}])]))
|
||||
|
||||
(mf/defc sets-list
|
||||
[{:keys []}]
|
||||
(let [token-sets (mf/deref refs/workspace-token-sets)
|
||||
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
|
||||
selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)
|
||||
token-set-selected? (mf/use-callback
|
||||
(mf/deps selected-token-set-id)
|
||||
|
@ -77,11 +173,11 @@
|
|||
(mf/deps active-token-set-ids)
|
||||
(fn [id]
|
||||
(get active-token-set-ids id)))]
|
||||
[:ul {:class (stl/css :sets-list)}
|
||||
(for [[id token-set] token-sets]
|
||||
[:& sets-tree
|
||||
{:key id
|
||||
:token-set token-set
|
||||
:selected-token-set-id selected-token-set-id
|
||||
:token-set-selected? token-set-selected?
|
||||
:token-set-active? token-set-active?}])]))
|
||||
[:& controlled-sets-list
|
||||
{:token-sets token-sets
|
||||
:token-set-selected? token-set-selected?
|
||||
:token-set-active? token-set-active?
|
||||
:on-select on-select-token-set-click
|
||||
:on-toggle-token-set on-toggle-token-set-click
|
||||
:on-update-token-set on-update-token-set
|
||||
:on-create-token-set on-create-token-set}]))
|
||||
|
|
|
@ -126,3 +126,21 @@
|
|||
transform: rotate(var(--chevron-icon-rotation));
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
.editing-node {
|
||||
@include textEllipsis;
|
||||
color: var(--layer-row-foreground-color-focus);
|
||||
}
|
||||
|
||||
input.editing-node {
|
||||
@include textEllipsis;
|
||||
@include bodySmallTypography;
|
||||
@include removeInputStyle;
|
||||
flex-grow: 1;
|
||||
height: $s-28;
|
||||
padding-left: $s-6;
|
||||
margin: 0;
|
||||
border-radius: $br-8;
|
||||
border: $s-1 solid var(--input-border-color-focus);
|
||||
color: var(--layer-row-foreground-color);
|
||||
}
|
||||
|
|
41
frontend/src/app/main/ui/workspace/tokens/sets_context.cljs
Normal file
41
frontend/src/app/main/ui/workspace/tokens/sets_context.cljs
Normal file
|
@ -0,0 +1,41 @@
|
|||
(ns app.main.ui.workspace.tokens.sets-context
|
||||
(:require
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def initial {:editing-id nil
|
||||
:new? false})
|
||||
|
||||
(def context (mf/create-context initial))
|
||||
|
||||
(def static-context
|
||||
{:editing? (constantly false)
|
||||
:new? false
|
||||
:on-edit (constantly nil)
|
||||
:on-create (constantly nil)
|
||||
:on-reset (constantly nil)})
|
||||
|
||||
(mf/defc provider
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [children (unchecked-get props "children")
|
||||
state (mf/use-state initial)]
|
||||
[:& (mf/provider context) {:value state}
|
||||
children]))
|
||||
|
||||
(defn use-context []
|
||||
(let [ctx (mf/use-ctx context)
|
||||
{:keys [editing-id new?]} @ctx
|
||||
editing? (mf/use-callback
|
||||
(mf/deps editing-id)
|
||||
#(= editing-id %))
|
||||
on-edit (mf/use-fn
|
||||
#(swap! ctx assoc :editing-id %))
|
||||
on-create (mf/use-fn
|
||||
#(swap! ctx assoc :editing-id (random-uuid) :new? true))
|
||||
on-reset (mf/use-fn
|
||||
#(reset! ctx initial))]
|
||||
{:editing? editing?
|
||||
:new? new?
|
||||
:on-edit on-edit
|
||||
:on-create on-create
|
||||
:on-reset on-reset}))
|
|
@ -14,6 +14,7 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||
|
@ -21,7 +22,9 @@
|
|||
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
|
||||
[app.main.ui.workspace.tokens.core :as wtc]
|
||||
[app.main.ui.workspace.tokens.sets :refer [sets-list]]
|
||||
[app.main.ui.workspace.tokens.sets-context :as sets-context]
|
||||
[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-types :as wtty]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -107,6 +110,7 @@
|
|||
(let [{:keys [key fields]} modal]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dt/set-token-type-section-open type true))
|
||||
(js/console.log "key" key)
|
||||
(modal/show! key {:x (.-clientX ^js event)
|
||||
:y (.-clientY ^js event)
|
||||
:position :right
|
||||
|
@ -179,74 +183,47 @@
|
|||
:name @name}))}
|
||||
"Create"]]))
|
||||
|
||||
(mf/defc edit-button
|
||||
[{:keys [create?]}]
|
||||
[:button {:class (stl/css :themes-button)
|
||||
:on-click (fn [e]
|
||||
(dom/stop-propagation e)
|
||||
(modal/show! :tokens/themes {}))}
|
||||
(if create? "Create" "Edit")])
|
||||
|
||||
(mf/defc themes-sidebar
|
||||
[_props]
|
||||
(let [open? (mf/use-state true)
|
||||
active-theme-ids (mf/deref refs/workspace-active-theme-ids)
|
||||
themes (mf/deref refs/workspace-ordered-token-themes)]
|
||||
[:div {:class (stl/css :sets-sidebar)}
|
||||
[:div {:class (stl/css :sidebar-header)}
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not @open?)
|
||||
:all-clickable true
|
||||
:title "THEMES"
|
||||
:on-collapsed #(swap! open? not)}]]
|
||||
(when @open?
|
||||
[:div
|
||||
[:style
|
||||
(str "@scope {"
|
||||
(str/join "\n"
|
||||
["ul { list-style-type: circle; margin-left: 20px; }"
|
||||
".spaced { display: flex; gap: 10px; justify-content: space-between; }"
|
||||
".spaced-y { display: flex; flex-direction: column; gap: 10px }"
|
||||
".selected { font-weight: 600; }"
|
||||
"b { font-weight: 600; }"])
|
||||
"}")]
|
||||
[:div.spaced-y
|
||||
{:style {:padding "10px"}}
|
||||
[:& tokene-theme-create]
|
||||
[:div.spaced-y
|
||||
[:b "Themes"]
|
||||
[:ul
|
||||
(for [[group themes] themes]
|
||||
[:li
|
||||
{:key (str "token-theme-group" group)}
|
||||
group
|
||||
[:ul
|
||||
(for [{:keys [id name] :as _theme} themes]
|
||||
[:li {:key (str "tokene-theme-" id)}
|
||||
[:div.spaced
|
||||
name
|
||||
[:div.spaced
|
||||
[:button
|
||||
{:on-click (fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (wdt/toggle-token-theme id)))}
|
||||
(if (get active-theme-ids id) "✅" "❎")]
|
||||
[:button {:on-click (fn [e]
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (wdt/delete-token-theme id)))}
|
||||
"🗑️"]]]])]])]]]])]))
|
||||
(let [ordered-themes (mf/deref refs/workspace-ordered-token-themes)]
|
||||
[:div {:class (stl/css :theme-sidebar)}
|
||||
[:span {:class (stl/css :themes-header)} "Themes"]
|
||||
[:div {:class (stl/css :theme-select-wrapper)}
|
||||
[:& theme-select]
|
||||
[:& edit-button {:create? (empty? ordered-themes)}]]]))
|
||||
|
||||
(mf/defc add-set-button
|
||||
[{:keys [on-open]}]
|
||||
(let [{:keys [on-create]} (sets-context/use-context)]
|
||||
[:button {:class (stl/css :add-set)
|
||||
:on-click #(do
|
||||
(on-open)
|
||||
(on-create))}
|
||||
i/add]))
|
||||
|
||||
(mf/defc sets-sidebar
|
||||
[]
|
||||
(let [open? (mf/use-state true)]
|
||||
[:div {:class (stl/css :sets-sidebar)}
|
||||
[:div {:class (stl/css :sidebar-header)}
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not @open?)
|
||||
:all-clickable true
|
||||
:title "SETS"
|
||||
:on-collapsed #(swap! open? not)}]
|
||||
[:button {:class (stl/css :add-set)
|
||||
:on-click #(do
|
||||
(reset! open? true)
|
||||
(on-set-add-click %))}
|
||||
i/add]]
|
||||
(when @open?
|
||||
[:& sets-list])]))
|
||||
(let [open? (mf/use-state true)
|
||||
on-open (mf/use-fn #(reset! open? true))]
|
||||
[:& sets-context/provider {}
|
||||
[:div {:class (stl/css :sets-sidebar)}
|
||||
[:div {:class (stl/css :sidebar-header)}
|
||||
[:& title-bar {:collapsable true
|
||||
:collapsed (not @open?)
|
||||
:all-clickable true
|
||||
:title "SETS"
|
||||
:on-collapsed #(swap! open? not)}
|
||||
[:& add-set-button {:on-open on-open}]]]
|
||||
(when @open?
|
||||
[:& sets-list])]]))
|
||||
|
||||
(mf/defc tokens-explorer
|
||||
[_props]
|
||||
|
@ -306,13 +283,24 @@
|
|||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[_props]
|
||||
(let [show-sets-section? (deref (temp-use-themes-flag))]
|
||||
(let [show-sets-section? (deref (temp-use-themes-flag))
|
||||
{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 :sitemap 200 38 400 :y false nil)]
|
||||
[:div {:class (stl/css :sidebar-tab-wrapper)}
|
||||
(when show-sets-section?
|
||||
[:div {:class (stl/css :sets-section-wrapper)}
|
||||
[:div {:class (stl/css :sets-section-wrapper)
|
||||
:style {:height (str size-pages-opened "px")}}
|
||||
[:& themes-sidebar]
|
||||
[:& sets-sidebar]])
|
||||
[:div {:class (stl/css :tokens-section-wrapper)}
|
||||
(when show-sets-section?
|
||||
[: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-explorer]]
|
||||
[:button {:class (stl/css :download-json-button)
|
||||
:on-click wtc/download-tokens-as-json}
|
||||
|
|
|
@ -18,12 +18,18 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: $s-8;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sets-sidebar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.theme-sidebar {
|
||||
padding: $s-12;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -114,3 +120,30 @@
|
|||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-select-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 0.28fr;
|
||||
gap: $s-6;
|
||||
}
|
||||
|
||||
.themes-button {
|
||||
@extend .button-secondary;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.themes-header {
|
||||
display: block;
|
||||
@include headlineSmallTypography;
|
||||
margin-bottom: $s-8;
|
||||
padding-left: $s-8;
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.resize-area-horiz {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
border-bottom: $s-2 solid var(--resize-area-border-color);
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
|
95
frontend/src/app/main/ui/workspace/tokens/theme_select.cljs
Normal file
95
frontend/src/app/main/ui/workspace/tokens/theme_select.cljs
Normal file
|
@ -0,0 +1,95 @@
|
|||
;; 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.theme-select
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.tokens :as wdt]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc themes-list
|
||||
[{:keys [themes active-theme-ids on-close grouped?]}]
|
||||
(when (seq themes)
|
||||
[:ul
|
||||
(for [{:keys [id name]} themes
|
||||
:let [selected? (get active-theme-ids id)]]
|
||||
[:li {:key id
|
||||
:class (stl/css-case
|
||||
:checked-element true
|
||||
:sub-item grouped?
|
||||
:is-selected selected?)
|
||||
:on-click (fn [e]
|
||||
(dom/stop-propagation e)
|
||||
(st/emit! (wdt/toggle-token-theme id))
|
||||
(on-close))}
|
||||
[:span {:class (stl/css :label)} name]
|
||||
[:span {:class (stl/css :check-icon)} i/tick]])]))
|
||||
|
||||
(mf/defc theme-options
|
||||
[{:keys [on-close]}]
|
||||
(let [active-theme-ids (mf/deref refs/workspace-active-theme-ids)
|
||||
ordered-themes (mf/deref refs/workspace-ordered-token-themes)
|
||||
grouped-themes (dissoc ordered-themes nil)
|
||||
ungrouped-themes (get ordered-themes nil)]
|
||||
[:ul
|
||||
[:& themes-list {:themes ungrouped-themes
|
||||
:active-theme-ids active-theme-ids
|
||||
:on-close on-close}]
|
||||
(for [[group themes] grouped-themes]
|
||||
[:li {:key group}
|
||||
(when group
|
||||
[:span {:class (stl/css :group)} group])
|
||||
[:& themes-list {:themes themes
|
||||
:active-theme-ids active-theme-ids
|
||||
:on-close on-close
|
||||
:grouped? true}]])
|
||||
[:li {:class (stl/css-case :checked-element true
|
||||
:checked-element-button true)
|
||||
:on-click #(modal/show! :tokens/themes {})}
|
||||
[:span "Edit themes"]
|
||||
[:span {:class (stl/css :icon)} i/arrow]]]))
|
||||
|
||||
(mf/defc theme-select
|
||||
[{:keys []}]
|
||||
(let [;; Store
|
||||
temp-theme-id (mf/deref refs/workspace-temp-theme-id)
|
||||
active-theme-ids (-> (mf/deref refs/workspace-active-theme-ids)
|
||||
(disj temp-theme-id))
|
||||
active-themes-count (count active-theme-ids)
|
||||
themes (mf/deref refs/workspace-token-themes)
|
||||
|
||||
;; Data
|
||||
current-label (cond
|
||||
(> active-themes-count 1) (str active-themes-count " themes active")
|
||||
(pos? active-themes-count) (get-in themes [(first active-theme-ids) :name])
|
||||
:else "No theme active")
|
||||
|
||||
;; State
|
||||
state* (mf/use-state
|
||||
{:id (uuid/next)
|
||||
:is-open? false})
|
||||
state (deref state*)
|
||||
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 (mf/use-fn #(swap! state* assoc :is-open? true))]
|
||||
[:div {:on-click on-open-dropdown
|
||||
:class (stl/css :custom-select)}
|
||||
[:span {:class (stl/css :current-label)} current-label]
|
||||
[:span {:class (stl/css :dropdown-button)} i/arrow]
|
||||
[:& dropdown {:show is-open? :on-close on-close-dropdown}
|
||||
[:div {:ref dropdown-element*
|
||||
:class (stl/css :custom-select-dropdown)}
|
||||
[:& theme-options {:on-close on-close-dropdown}]]]]))
|
167
frontend/src/app/main/ui/workspace/tokens/theme_select.scss
Normal file
167
frontend/src/app/main/ui/workspace/tokens/theme_select.scss
Normal file
|
@ -0,0 +1,167 @@
|
|||
// 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";
|
||||
|
||||
.custom-select {
|
||||
--border-color: var(--menu-background-color);
|
||||
--bg-color: var(--menu-background-color);
|
||||
--icon-color: var(--icon-foreground);
|
||||
--text-color: var(--menu-foreground-color);
|
||||
@extend .new-scrollbar;
|
||||
@include bodySmallTypography;
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
height: $s-32;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: $s-8;
|
||||
border-radius: $br-8;
|
||||
background-color: var(--bg-color);
|
||||
border: $s-1 solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: block;
|
||||
@include headlineSmallTypography;
|
||||
padding: $s-8;
|
||||
color: var(--color-foreground-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.icon {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
--bg-color: var(--menu-background-color-hover);
|
||||
--border-color: var(--menu-background-color);
|
||||
--icon-color: var(--menu-foreground-color-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
--bg-color: var(--menu-background-color-focus);
|
||||
--border-color: var(--menu-background-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
--bg-color: var(--menu-background-color-disabled);
|
||||
--border-color: var(--menu-border-color-disabled);
|
||||
--icon-color: var(--menu-foreground-color-disabled);
|
||||
--text-color: var(--menu-foreground-color-disabled);
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
@include flexCenter;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
transform: rotate(90deg);
|
||||
stroke: var(--icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
.current-icon {
|
||||
@include flexCenter;
|
||||
width: $s-24;
|
||||
padding-right: $s-4;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-select-dropdown {
|
||||
@extend .dropdown-wrapper;
|
||||
.separator {
|
||||
margin: 0;
|
||||
height: $s-12;
|
||||
border-block-start: $s-1 solid var(--dropdown-separator-color);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-select-dropdown[data-direction="up"] {
|
||||
bottom: $s-32;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
.sub-item {
|
||||
padding-left: $s-16;
|
||||
}
|
||||
|
||||
.checked-element-button {
|
||||
@extend .dropdown-element-base;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
li + .checked-element-button {
|
||||
margin-top: $s-8;
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -$s-4;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: color-mix(in hsl, var(--color-foreground-secondary) 20%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.checked-element {
|
||||
@extend .dropdown-element-base;
|
||||
|
||||
.icon {
|
||||
@include flexCenter;
|
||||
height: $s-24;
|
||||
width: $s-24;
|
||||
padding-right: $s-4;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
@include flexCenter;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
visibility: hidden;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
color: var(--menu-foreground-color);
|
||||
.check-icon svg {
|
||||
stroke: var(--menu-foreground-color);
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.current-label {
|
||||
@include textEllipsis;
|
||||
}
|
|
@ -12,6 +12,9 @@
|
|||
(defn get-workspace-themes [state]
|
||||
(get-in state [:workspace-data :token-themes] []))
|
||||
|
||||
(defn get-workspace-theme [id state]
|
||||
(get-in state [:workspace-data :token-themes-index id]))
|
||||
|
||||
(defn get-workspace-themes-index [state]
|
||||
(get-in state [:workspace-data :token-themes-index] {}))
|
||||
|
||||
|
@ -69,7 +72,7 @@
|
|||
|
||||
(defn toggle-active-theme-id
|
||||
"Toggle a `theme-id` by checking `:token-active-themes`.
|
||||
De-activate all theme-ids that have the same group as `theme-id` when activating `theme-id`.
|
||||
Deactivate all theme-ids that have the same group as `theme-id` when activating `theme-id`.
|
||||
Ensures that the temporary theme id is selected when the resulting set is empty."
|
||||
[theme-id state]
|
||||
(let [temp-theme-id-set (some->> (get-temp-theme-id state) (conj #{}))
|
||||
|
@ -112,6 +115,24 @@
|
|||
(defn get-workspace-sets [state]
|
||||
(get-in state [:workspace-data :token-sets-index]))
|
||||
|
||||
(defn get-workspace-ordered-sets [state]
|
||||
;; TODO Include groups
|
||||
(let [top-level-set-ids (get-in state [:workspace-data :token-set-groups])
|
||||
token-sets (get-workspace-sets state)]
|
||||
(->> (map (fn [id] [id (get token-sets id)]) top-level-set-ids)
|
||||
(into (ordered-map)))))
|
||||
|
||||
(defn get-workspace-ordered-sets-tokens [state]
|
||||
(let [sets (get-workspace-ordered-sets state)]
|
||||
(reduce
|
||||
(fn [acc [_ {:keys [tokens] :as sets}]]
|
||||
(reduce (fn [acc' token-id]
|
||||
(if-let [token (wtt/get-workspace-token token-id state)]
|
||||
(assoc acc' (wtt/token-identifier token) token)
|
||||
acc'))
|
||||
acc tokens))
|
||||
{} sets)))
|
||||
|
||||
(defn get-token-set [set-id state]
|
||||
(some-> (get-workspace-sets state)
|
||||
(get set-id)))
|
||||
|
|
Loading…
Reference in a new issue