0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-06 14:50:20 -05:00

♻️ Review themes section

This commit is contained in:
Eva Marco 2024-10-23 16:28:30 +02:00
parent 03ea5414be
commit 0ff5df4b8d
17 changed files with 665 additions and 443 deletions

View file

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round">
<path d="m4 6 4 4 4-4"/>
</svg>

After

Width:  |  Height:  |  Size: 144 B

View file

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 12 6 8l4-4"/>
</svg>

After

Width:  |  Height:  |  Size: 145 B

View file

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 12 4-4-4-4"/>
</svg>

After

Width:  |  Height:  |  Size: 144 B

View file

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-linejoin="round">
<path d="m4 10 4-4 4 4"/>
</svg>

After

Width:  |  Height:  |  Size: 140 B

View file

@ -50,6 +50,10 @@
(def ^:icon-id align-top "align-top")
(def ^:icon-id align-vertical-center "align-vertical-center")
(def ^:icon-id arrow "arrow")
(def ^:icon-id arrow-up "arrow-up")
(def ^:icon-id arrow-down "arrow-down")
(def ^:icon-id arrow-left "arrow-left")
(def ^:icon-id arrow-right "arrow-right")
(def ^:icon-id asc-sort "asc-sort")
(def ^:icon-id board "board")
(def ^:icon-id boards-thumbnail "boards-thumbnail")

View file

@ -32,17 +32,17 @@
(let [level (or level "1")
tag (dm/str "h" level)
class (dm/str (or class "") " " (stl/css-case :display-typography (= typography t/display)
:title-large-typography (= typography t/title-large)
:title-medium-typography (= typography t/title-medium)
:title-small-typography (= typography t/title-small)
:headline-large-typography (= typography t/headline-large)
:headline-medium-typography (= typography t/headline-medium)
:headline-small-typography (= typography t/headline-small)
:body-large-typography (= typography t/body-large)
:body-medium-typography (= typography t/body-medium)
:body-small-typography (= typography t/body-small)
:code-font-typography (= typography t/code-font)))
class (dm/str class " " (stl/css-case :display-typography (= typography t/display)
:title-large-typography (= typography t/title-large)
:title-medium-typography (= typography t/title-medium)
:title-small-typography (= typography t/title-small)
:headline-large-typography (= typography t/headline-large)
:headline-medium-typography (= typography t/headline-medium)
:headline-small-typography (= typography t/headline-small)
:body-large-typography (= typography t/body-large)
:body-medium-typography (= typography t/body-medium)
:body-small-typography (= typography t/body-small)
:code-font-typography (= typography t/code-font)))
props (mf/spread-props props {:class class})]
[:> tag props
children]))

View file

@ -19,6 +19,7 @@
[app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
@ -353,14 +354,14 @@ Token names should only contain letters and digits separated by . characters.")}
[:form {:class (stl/css :form-wrapper)
:on-submit on-submit}
[:div {:class (stl/css :token-rows)}
[:> text* {:as "span" :typography "headline-medium"}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
(if (= action "edit")
(tr "workspace.token.edit-token")
(tr "workspace.token.create-token" token-type))]
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span "Name"]
[:span {:class (stl/css :labeled-input-label)} "Name"]
[:& tokens.common/labeled-input {:label "Name"
:error? @name-errors
:input-props {:default-value @name-ref
@ -378,7 +379,7 @@ Token names should only contain letters and digits separated by . characters.")}
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span "value"]
[:span {:class (stl/css :labeled-input-label)} "value"]
[:& tokens.common/labeled-input {:label "Value"
:input-props {:default-value @value-ref
:on-blur on-update-value
@ -401,7 +402,7 @@ Token names should only contain letters and digits separated by . characters.")}
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span "Description"]
[:span {:class (stl/css :labeled-input-label)} "Description"]
[:& tokens.common/labeled-input {:label "Description"
:input-props {:default-value @description-ref
:on-change on-update-description}}]
@ -416,10 +417,12 @@ Token names should only contain letters and digits separated by . characters.")}
(when (= action "edit")
[:> button* {:on-click on-delete-token
:class (stl/css :delete-btn)
:type "button"
:icon i/delete
:variant "secondary"}
(tr "labels.delete")])
[:> button* {:on-click on-cancel
:type "button"
:variant "secondary"}
(tr "labels.cancel")]
[:> button* {:type "submit"

View file

@ -39,6 +39,10 @@
gap: $s-4;
}
.labeled-input-label {
color: var(--color-foreground-primary);
}
.error {
padding: $s-4 $s-6;
margin-bottom: 0;
@ -75,3 +79,7 @@
border-radius: $br-4;
cursor: pointer;
}
.form-modal-title {
color: var(--color-foreground-primary);
}

View file

@ -12,7 +12,6 @@
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.workspace.tokens.form :refer [form]]
[app.main.ui.workspace.tokens.modals.themes :as wtmt]
[app.util.i18n :refer [tr]]
[okulary.core :as l]
[rumext.v2 :as mf]))
@ -60,12 +59,6 @@
:selected-token-set-id selected-token-set-id
: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

View file

@ -7,36 +7,49 @@
(ns app.main.ui.workspace.tokens.modals.themes
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[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.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.common :refer [labeled-input] :as wtco]
[app.main.ui.workspace.tokens.sets :as wts]
[app.main.ui.workspace.tokens.sets-context :as sets-context]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(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"]]])
(let [create-theme
(mf/use-fn
(mf/deps set-state)
#(set-state (fn [_] {:type :create-theme})))]
[:div {:class (stl/css :themes-modal-wrapper)}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)}
(tr "workspace.token.themes")]
[:div {:class (stl/css :empty-themes-wrapper)}
[:div {:class (stl/css :empty-themes-message)}
[:> text* {:as "span" :typography "title-medium" :class (stl/css :empty-theme-title)}
(tr "workspace.token.no-themes-currently")]
[:> text* {:as "span"
:class (stl/css :empty-theme-subtitle)
:typography "body-medium"}
(tr "workspace.token.create-new-theme")]]
[:div {:class (stl/css :button-footer)}
[:> button* {:variant "primary"
:type "button"
:on-click create-theme}
(tr "workspace.token.new-theme")]]]]))
(mf/defc switch
[{:keys [selected? name on-change]}]
@ -57,60 +70,195 @@
[{:keys [set-state]}]
(let [active-theme-ids (mf/deref refs/workspace-active-theme-paths)
themes-groups (mf/deref refs/workspace-token-theme-tree-no-hidden)
on-edit-theme (fn [theme e]
(dom/prevent-default e)
(dom/stop-propagation e)
(set-state (fn [_] {:type :edit-theme
:theme-path [(:id theme) (:group theme) (:name theme)]})))]
[:div
create-theme
(mf/use-fn
(mf/deps set-state)
(fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(set-state (fn [_] {:type :create-theme}))))]
[:div {:class (stl/css :themes-modal-wrapper)}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)}
(tr "workspace.token.themes")]
[:ul {:class (stl/css :theme-group-wrapper)}
(for [[group themes] themes-groups]
[:li {:key (str "token-theme-group" group)}
[:li {:key (dm/str "token-theme-group" group)}
(when (seq group)
[:span {:class (stl/css :theme-group-label)} group])
[:> heading* {:level 3
:class (stl/css :theme-group-label)
:typography "body-large"}
[:span {:class (stl/css :group-title)}
[:> icon* {:id "group"}]
group]])
[:ul {:class (stl/css :theme-group-rows-wrapper)}
(for [[_ {:keys [group name] :as theme}] themes
:let [theme-id (ctob/theme-path theme)
selected? (some? (get active-theme-ids theme-id))]]
selected? (some? (get active-theme-ids theme-id))
delete-theme
(fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(st/emit! (wdt/delete-token-theme group name)))
on-edit-theme
(fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(set-state (fn [_] {:type :edit-theme
:theme-path [(:id theme) (:group theme) (:name theme)]})))]]
[:li {:key theme-id
:class (stl/css :theme-row)}
[:div {:class (stl/css :theme-row-left)}
;; FIREEEEEEEEEE THIS
[:div {:on-click (fn [e]
(dom/prevent-default e)
(dom/stop-propagation e)
(st/emit! (wdt/toggle-token-theme-active? group name)))}
[:& switch {:name (str "Theme" name)
[:& switch {:name (tr "workspace.token.theme" name)
:on-change (constantly nil)
:selected? selected?}]]
[:span {:class (stl/css :theme-row-label)} name]]
[:> text* {:as "span" :typography "body-medium" :class (stl/css :theme-name)} 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 group name)))}
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"]]]))
[:> button* {:class (stl/css :sets-count-button)
:variant "secondary"
:type "button"
:on-click on-edit-theme}
[:div {:class (stl/css :label-wrapper)}
[:> text* {:as "span" :typography "body-medium"}
(tr "workspace.token.num-sets" sets-count)]
[:> icon* {:id "arrow-right"}]]]
(mf/defc edit-theme
[{:keys [edit? token-sets theme theme-groups on-back on-submit]}]
(let [{:keys [dropdown-open? on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state)
[:> button* {:class (stl/css :sets-count-empty-button)
:type "button"
:variant "secondary"
:on-click on-edit-theme}
[:div {:class (stl/css :label-wrapper)}
[:> text* {:as "span" :typography "body-medium"}
(tr "workspace.token.no-sets")]
[:> icon* {:id "arrow-right"}]]])
[:> icon-button* {:on-click delete-theme
:variant "ghost"
:aria-label (tr "workspace.token.delete-theme-title")
:icon "delete"}]]])]])]
[:div {:class (stl/css :button-footer)}
[:> button* {:variant "primary"
:type "button"
:icon "add"
:on-click create-theme}
(tr "workspace.token.create-theme-title")]]]))
(mf/defc theme-inputs
[{:keys [theme dropdown-open? on-close-dropdown on-toggle-dropdown on-change-field]}]
(let [theme-groups (mf/deref refs/workspace-token-theme-groups)
group-input-ref (mf/use-ref)
on-update-group (partial on-change-field :group)
on-update-name (partial on-change-field :name)]
[:div {:class (stl/css :edit-theme-inputs-wrapper)}
[:div {:class (stl/css :group-input-wrapper)}
(when dropdown-open?
[:& wtco/dropdown-select {:id ::groups-dropdown
:shortcuts-key ::groups-dropdown
:options (map (fn [group]
{:label group
:value group})
theme-groups)
:on-select (fn [{:keys [value]}]
(set! (.-value (mf/ref-val group-input-ref)) value)
(on-update-group value))
:on-close on-close-dropdown}])
;; TODO: This span should be remove when labeled-input is updated
[:span {:class (stl/css :labeled-input-label)} "Theme group"]
[:& labeled-input {:label "Group"
:input-props {:ref group-input-ref
:default-value (:group theme)
:on-change (comp on-update-group dom/get-target-val)}
:render-right (when (seq theme-groups)
(mf/fnc []
[:button {:class (stl/css :group-drop-down-button)
:type "button"
:on-click (fn [e]
(dom/stop-propagation e)
(on-toggle-dropdown))}
[:> icon* {:id "arrow-down"}]]))}]]
[:div {:class (stl/css :group-input-wrapper)}
;; TODO: This span should be remove when labeled-input is updated
[:span {:class (stl/css :labeled-input-label)} "Theme"]
[:& labeled-input {:label "Theme"
:input-props {:default-value (:name theme)
:on-change (comp on-update-name dom/get-target-val)}}]]]))
(mf/defc theme-modal-buttons
[{:keys [close-modal on-save-form disabled?] :as props}]
[:*
[:> button* {:variant "secondary"
:type "button"
:on-click close-modal}
(tr "labels.cancel")]
[:> button* {:variant "primary"
:type "submit"
:on-click on-save-form
:disabled disabled?}
(tr "workspace.token.save-theme")]])
(mf/defc create-theme
[{:keys [set-state]}]
(let [{:keys [dropdown-open? _on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state)
theme (ctob/make-token-theme :name "")
on-back #(set-state (constantly {:type :themes-overview}))
on-submit #(st/emit! (wdt/create-token-theme %))
theme-state (mf/use-state theme)
disabled? (-> (:name @theme-state)
(str/trim)
(str/empty?))
on-change-field (fn [field value]
(swap! theme-state #(assoc % field value)))
on-save-form (mf/use-callback
(mf/deps theme-state on-submit)
(fn [e]
(dom/prevent-default e)
(let [theme (-> @theme-state
(update :name str/trim)
(update :group str/trim)
(update :description str/trim))]
(when-not (str/empty? (:name theme))
(on-submit theme)))
(on-back)))
close-modal (mf/use-fn
(fn [e]
(dom/prevent-default e)
(st/emit! (modal/hide))))]
[:div {:class (stl/css :themes-modal-wrapper)}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)}
(tr "workspace.token.create-theme-title")]
[:form {:on-submit on-save-form}
[:div {:class (stl/css :create-theme-wrapper)}
[:& theme-inputs {:dropdown-open? dropdown-open?
:on-close-dropdown on-close-dropdown
:on-toggle-dropdown on-toggle-dropdown
:theme theme
:on-change-field on-change-field}]
[:div {:class (stl/css :button-footer)}
[:& theme-modal-buttons {:close-modal close-modal
:on-save-form on-save-form
:disabled? disabled?}]]]]]))
(mf/defc controlled-edit-theme
[{:keys [state set-state]}]
(let [{:keys [theme-path]} @state
[_ theme-group theme-name] theme-path
token-sets (mf/deref refs/workspace-ordered-token-sets)
theme (mf/deref (refs/workspace-token-theme theme-group theme-name))
on-back #(set-state (constantly {:type :themes-overview}))
on-submit #(st/emit! (wdt/update-token-theme [(:group theme) (:name theme)] %))
{:keys [dropdown-open? _on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state)
theme-state (mf/use-state theme)
disabled? (-> (:name @theme-state)
(str/trim)
@ -125,9 +273,6 @@
(swap! theme-state #(ctob/toggle-set % set-name))))
on-change-field (fn [field value]
(swap! theme-state #(assoc % field value)))
group-input-ref (mf/use-ref)
on-update-group (partial on-change-field :group)
on-update-name (partial on-change-field :name)
on-save-form (mf/use-callback
(mf/deps theme-state on-submit)
(fn [e]
@ -138,124 +283,83 @@
(update :description str/trim))]
(when-not (str/empty? (:name theme))
(on-submit theme)))
(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)}
[:div {:class (stl/css :group-input-wrapper)}
(when dropdown-open?
[:& wtco/dropdown-select {:id ::groups-dropdown
:shortcuts-key ::groups-dropdown
:options (map (fn [group]
{:label group
:value group})
theme-groups)
:on-select (fn [{:keys [value]}]
(set! (.-value (mf/ref-val group-input-ref)) value)
(on-update-group value))
:on-close on-close-dropdown}])
[:& labeled-input {:label "Group"
:input-props {:ref group-input-ref
:default-value (:group theme)
:on-change (comp on-update-group dom/get-target-val)}
:render-right (when (seq theme-groups)
(mf/fnc []
[:button {:class (stl/css :group-drop-down-button)
:type "button"
:on-click (fn [e]
(dom/stop-propagation e)
(on-toggle-dropdown))}
i/arrow]))}]]
[:& labeled-input {:label "Theme"
:input-props {:default-value (:name theme)
:on-change (comp on-update-name dom/get-target-val)}}]]
[: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 (:group theme) (:name 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"]]]]]))
(on-back)))
close-modal
(mf/use-fn
(fn [e]
(dom/prevent-default e)
(st/emit! (modal/hide))))
(mf/defc controlled-edit-theme
[{:keys [state set-state]}]
(let [{:keys [theme-path]} @state
[_ theme-group theme-name] theme-path
token-sets (mf/deref refs/workspace-ordered-token-sets)
theme (mf/deref (refs/workspace-token-theme theme-group theme-name))
theme-groups (mf/deref refs/workspace-token-theme-groups)]
[:& edit-theme
{:edit? true
:token-sets token-sets
:theme theme
:theme-groups theme-groups
:on-back #(set-state (constantly {:type :themes-overview}))
:on-submit #(st/emit! (wdt/update-token-theme [(:group theme) (:name theme)] %))}]))
on-delete-token
(mf/use-fn
(mf/deps theme on-back)
(fn []
(st/emit! (wdt/delete-token-theme (:group theme) (:name theme)))
(on-back)))]
(mf/defc create-theme
[{:keys [set-state]}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
theme (ctob/make-token-theme :name "")
theme-groups (mf/deref refs/workspace-token-theme-groups)]
[:& edit-theme
{:edit? false
:token-sets token-sets
:theme theme
:theme-groups theme-groups
:on-back #(set-state (constantly {:type :themes-overview}))
:on-submit #(st/emit! (wdt/create-token-theme %))}]))
[:div {:class (stl/css :themes-modal-wrapper)}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)}
(tr "workspace.token.edit-theme-title")]
(mf/defc themes
[:form {:on-submit on-save-form}
[:div {:class (stl/css :edit-theme-wrapper)}
[:button {:on-click on-back
:class (stl/css :back-btn)
:type "button"}
[:> icon* {:id ic/arrow-left :aria-hidden true}]
(tr "workspace.token.back-to-themes")]
[:& theme-inputs {:dropdown-open? dropdown-open?
:on-close-dropdown on-close-dropdown
:on-toggle-dropdown on-toggle-dropdown
:theme theme
:on-change-field on-change-field}]
[: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)}
[:> button* {:variant "secondary"
:type "button"
:on-click on-delete-token}
(tr "labels.delete")]
[:div {:class (stl/css :button-footer)}
[:& theme-modal-buttons {:close-modal close-modal
:on-save-form on-save-form
:disabled? disabled?}]]]]]]))
(mf/defc themes-modal-body
[_]
(let [themes (mf/deref refs/workspace-token-themes-no-hidden)
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}]]]))
[:& component {:state state
:set-state set-state}]))
(mf/defc modal
{::mf/wrap-props false}
[_]
(mf/defc token-themes-modal
{::mf/wrap-props false
::mf/register modal/components
::mf/register-as :tokens/themes}
[_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]]]))
[:> icon-button* {:class (stl/css :close-btn)
:on-click handle-close-dialog
:aria-label (tr "labels.close")
:variant "action"
:icon "close"}]
[:& themes-modal-body]]]))

View file

@ -10,10 +10,6 @@
@extend .modal-overlay-base;
}
hr {
border-color: var(--color-background-tertiary);
}
.modal-dialog {
@extend .modal-container-base;
display: grid;
@ -23,16 +19,43 @@ hr {
user-select: none;
}
.modal-title {
@include headlineMediumTypography;
font-weight: 500;
margin-block-end: $s-16;
color: var(--color-foreground-secondary);
}
.modal-content {
.empty-themes-message {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: $s-12;
padding: $s-72 0;
}
.themes-modal-wrapper {
display: flex;
flex-direction: column;
gap: $s-24;
}
.themes-modal-title {
color: var(--color-foreground-primary);
}
.back-btn {
background-color: transparent;
border: none;
appearance: none;
color: var(--color-foreground-secondary);
width: fit-content;
display: grid;
grid-template-columns: auto auto;
gap: $s-4;
align-items: center;
padding: 0;
&:hover {
color: var(--color-accent-primary);
}
}
.labeled-input-label {
color: var(--color-foreground-primary);
}
.button-footer {
@ -46,60 +69,41 @@ hr {
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);
}
}
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);
}
.empty-theme-subtitle {
color: var(--color-foreground-secondary);
}
.empty-theme-title {
color: var(--color-foreground-primary);
}
.create-theme-wrapper {
display: flex;
flex-direction: column;
gap: $s-24;
}
.close-btn {
@extend .modal-close-btn-base;
position: absolute;
top: $s-8;
right: $s-6;
}
.theme-group-label {
display: block;
@include headlineMediumTypography;
color: var(--color-foreground-secondary);
margin-bottom: $s-8;
}
.group-title {
display: flex;
align-items: center;
justify-content: flex-start;
gap: $s-4;
}
.theme-group-rows-wrapper {
@ -127,34 +131,26 @@ hr {
gap: $s-16;
}
.theme-name {
color: var(--color-foreground-primary);
}
.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;
text-transform: lowercase;
padding: $s-6;
padding-left: $s-12;
svg {
margin-left: $s-6;
@extend .button-icon;
stroke: var(--icon-foreground);
}
}
.label-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.edit-theme-wrapper {
@ -163,12 +159,6 @@ hr {
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;
@ -176,57 +166,29 @@ hr {
}
.sets-count-empty-button {
@extend .button-secondary;
text-transform: lowercase;
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);
}
}
}
.group-input-wrapper {
position: relative;
display: flex;
flex-direction: column;
gap: $s-4;
}
.edit-theme-inputs-wrapper {
display: grid;
grid-template-columns: 0.6fr 1fr;
gap: $s-12;
}
.group-drop-down-button {
@include buttonStyle;
color: var(--color-foreground-secondary);
width: $s-24;
height: 100%;
padding: 0;
margin: 0 $s-6;
svg {
@extend .button-icon-small;
transform: rotate(90deg);
fill: var(--icon-foreground);
}
}

View file

@ -16,9 +16,10 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu
dropdown-menu-item*]]
[app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as h]
[app.main.ui.hooks.resize :refer [use-resize-hook]]
[app.main.ui.icons :as i]
@ -173,22 +174,26 @@
{:empty (sort-by :token-key empty)
:filled (sort-by :token-key filled)}))
(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? (tr "labels.create") (tr "labels.edit"))])
(mf/defc themes-header
[_props]
(let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)]
(let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden)
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")]
[:div {:class (stl/css :theme-select-wrapper)}
[:& theme-select]
[:& edit-button {:create? (empty? ordered-themes)}]]]))
(if (empty? ordered-themes)
[:div {:class (stl/css :empty-theme-wrapper)}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.token.no-themes")]
[:button {:on-click open-modal
:class (stl/css :create-theme-button)} (tr "workspace.token.create-a-theme")]]
[:div {:class (stl/css :theme-select-wrapper)}
[:& theme-select]
[:> button* {:variant "secondary"
:on-click open-modal}
(tr "labels.edit")]])]))
(mf/defc add-set-button
[{:keys [on-open]}]

View file

@ -4,6 +4,7 @@
//
// Copyright (c) KALEIDOS INC
@use "../../ds/typography.scss" as *;
@import "refactor/common-refactor.scss";
@import "./common.scss";
@ -37,9 +38,21 @@
position: relative;
}
.themes-header {
display: block;
@include headlineSmallTypography;
margin-bottom: $s-8;
padding-left: $s-8;
color: var(--title-foreground-color);
}
.themes-wrapper {
padding: $s-12 0 0 $s-12;
}
.empty-theme-wrapper {
padding: $s-12;
padding-bottom: 0;
color: var(--color-foreground-secondary);
}
.sidebar-header {
@ -168,12 +181,13 @@
width: auto;
}
.themes-header {
display: block;
@include headlineSmallTypography;
margin-bottom: $s-8;
padding-left: $s-8;
color: var(--title-foreground-color);
.create-theme-button {
@include use-typography("body-small");
background-color: transparent;
border: none;
appearance: none;
color: var(--color-accent-primary);
cursor: pointer;
}
.resize-area-horiz {

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.theme-select
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
@ -14,46 +15,62 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.icons :as i]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc themes-list
[{:keys [themes active-theme-paths on-close grouped?]}]
(when (seq themes)
[:ul
[:ul {:class (stl/css :theme-options)}
(for [[_ {:keys [group name] :as theme}] themes
:let [theme-id (ctob/theme-path theme)
selected? (get active-theme-paths theme-id)]]
selected? (get active-theme-paths theme-id)
select-theme (fn [e]
(dom/stop-propagation e)
(st/emit! (wdt/toggle-token-theme-active? group name))
(on-close))]]
[:li {:key theme-id
:role "option"
:aria-selected selected?
: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-active? group name))
(on-close))}
[:span {:class (stl/css :label)} name]
[:span {:class (stl/css :check-icon)} i/tick]])]))
:on-click select-theme}
[:> text* {:as "span" :typography "body-small" :class (stl/css :label)} name]
[:> icon* {:id i/tick
:aria-hidden true
:class (stl/css-case :check-icon true
:check-icon-visible selected?)}]])]))
(mf/defc theme-options
[{:keys [active-theme-paths themes on-close]}]
[:ul
(for [[group themes] themes]
[:li {:key group}
(when (seq group)
[:span {:class (stl/css :group)} group])
[:& themes-list {:themes themes
:active-theme-paths active-theme-paths
: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]]])
(let []
(let [on-edit-click #(modal/show! :tokens/themes {})]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str group "-label")} group])
[:& themes-list {:themes themes
:active-theme-paths active-theme-paths
:on-close on-close
:grouped? true}]])
[:li {:class (stl/css :separator)
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true
:checked-element-button true)
:role "option"
:on-click on-edit-click}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.token.edit-themes")]
[:> icon* {:id i/arrow-right :aria-hidden true}]]])))
(mf/defc theme-select
[{:keys []}]
@ -64,11 +81,11 @@
;; Data
current-label (cond
(> active-themes-count 1) (str active-themes-count " themes active")
(> active-themes-count 1) (tr "workspace.token.active-themes" active-themes-count)
(= active-themes-count 1) (some->> (first active-theme-paths)
(ctob/split-token-theme-path)
(str/join " / "))
:else "No theme active")
:else (tr "workspace.token.no-active-theme"))
;; State
state* (mf/use-state
@ -81,13 +98,20 @@
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))]
;; TODO: This element should be accessible by keyboard
[:div {:on-click on-open-dropdown
:aria-expanded is-open?
:aria-haspopup "listbox"
:tab-index "0"
:role "combobox"
: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 {:active-theme-paths active-theme-paths
:themes themes
:on-close on-close-dropdown}]]]]))
[:> text* {:as "span" :typography "body-small" :class (stl/css :current-label)}
current-label]
[:> 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*}
[:& theme-options {:active-theme-paths active-theme-paths
:themes themes
:on-close on-close-dropdown}]]]))

View file

@ -7,12 +7,11 @@
@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);
--custom-select-border-color: var(--menu-background-color);
--custom-select-bg-color: var(--menu-background-color);
--custom-select-icon-color: var(--color-foreground-secondary);
--custom-select-text-color: var(--menu-foreground-color);
@extend .new-scrollbar;
@include bodySmallTypography;
position: relative;
display: grid;
grid-template-columns: 1fr auto;
@ -22,74 +21,60 @@
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);
background-color: var(--custom-select-bg-color);
border: $s-1 solid var(--custom-select-border-color);
color: var(--custom-select-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);
--custom-select-bg-color: var(--menu-background-color-hover);
--custom-select-border-color: var(--menu-background-color);
--custom-select-icon-color: var(--menu-foreground-color-hover);
}
&:focus {
--bg-color: var(--menu-background-color-focus);
--border-color: var(--menu-background-focus);
--custom-select-bg-color: var(--menu-background-color-focus);
--custom-select-border-color: var(--menu-background-focus);
}
}
.theme-options {
margin-bottom: 0;
}
.group {
display: block;
padding: $s-8;
color: var(--color-foreground-secondary);
}
.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);
--custom-select-bg-color: var(--menu-background-color-disabled);
--custom-select-border-color: var(--menu-border-color-disabled);
--custom-select-icon-color: var(--menu-foreground-color-disabled);
--custom-select-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);
}
color: var(--color-foreground-secondary);
}
.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);
}
}
.separator {
margin: 0;
height: $s-2;
border-block-start: $s-1 solid color-mix(in hsl, var(--color-foreground-secondary) 20%, transparent);
}
.custom-select-dropdown[data-direction="up"] {
@ -109,59 +94,31 @@
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;
}
}
.check-icon {
@include flexCenter;
color: var(--icon-foreground-primary);
visibility: hidden;
}
.label {
flex-grow: 1;
width: 100%;
}
.check-icon-visible {
visibility: visible;
}
.current-label {
@include textEllipsis;
}

View file

@ -6163,4 +6163,72 @@ msgstr "Resolved value: "
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.original-value"
msgstr "Original value: "
msgstr "Original value: "
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.no-themes"
msgstr "There are no themes."
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.create-a-theme"
msgstr "Create one."
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.save-theme"
msgstr "Save theme"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.create-theme-title"
msgstr "Create theme"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.edit-theme-title"
msgstr "Edit theme"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.delete-theme-title"
msgstr "Delete theme"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.no-themes-currently"
msgstr "You currently have no themes."
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.create-new-theme"
msgstr "Create your first theme now."
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.new-theme"
msgstr "New theme"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.themes"
msgstr "Themes"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.theme-name"
msgstr "Theme %s"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.no-sets"
msgstr "No sets defined"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.num-sets"
msgstr "%s sets"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.back-to-themes"
msgstr "Back to theme list"
#: src/app/main/ui/workspace/tokens/theme_select.cljs
msgid "workspace.token.edit-themes"
msgstr "Edit themes"
#: src/app/main/ui/workspace/tokens/theme_select.cljs
msgid "workspace.token.no-active-theme"
msgstr "No theme active"
#: src/app/main/ui/workspace/tokens/theme_select.cljs
msgid "workspace.token.active-themes"
msgstr "%s active themes"

View file

@ -6150,4 +6150,72 @@ msgstr "Valor resuelto: "
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.original-value"
msgstr "Valor original: "
msgstr "Valor original: "
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.no-themes"
msgstr "No hay temas."
#: src/app/main/ui/workspace/tokens/sidebar.cljs
msgid "workspace.token.create-a-theme"
msgstr "Crear uno."
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.save-theme"
msgstr "Guardar tema"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.create-theme-title"
msgstr "Crear tema"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.edit-theme-title"
msgstr "Editar tema"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.delete-theme-title"
msgstr "Borrar theme"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.no-themes-currently"
msgstr "Actualmente no existen temas."
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.create-new-theme"
msgstr "Crea un nuevo tema ahora."
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.new-theme"
msgstr "Nuevo tema"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.themes"
msgstr "Temas"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.theme-name"
msgstr "Tema %s"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.no-sets"
msgstr "No hay sets definidos"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.num-sets"
msgstr "%s sets"
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
msgid "workspace.token.back-to-themes"
msgstr "Volver al listado de temas"
#: src/app/main/ui/workspace/tokens/theme_select.cljs
msgid "workspace.token.edit-themes"
msgstr "Editar temas"
#: src/app/main/ui/workspace/tokens/theme_select.cljs
msgid "workspace.token.no-active-theme"
msgstr "No hay temas activos"
#: src/app/main/ui/workspace/tokens/theme_select.cljs
msgid "workspace.token.active-themes"
msgstr "%s temas activos"