Merge tokens-studio/develop into develop

This commit is contained in:
Andrey Antukh 2024-11-21 16:08:10 +01:00
parent f9912e0299
commit 395962ae4d
37 changed files with 1121 additions and 516 deletions

View file

@ -95,6 +95,14 @@
(update [_ state]
(wtts/assoc-selected-token-set-id state id))))
(defn set-selected-token-set-id-from-name
[token-set-name]
(ptk/reify ::set-selected-token-set-id-from-name
ptk/UpdateEvent
(update [_ state]
(->> (ctob/set-name->set-path-string token-set-name)
(wtts/assoc-selected-token-set-id state)))))
(defn create-token-theme [token-theme]
(let [new-token-theme token-theme]
(ptk/reify ::create-token-theme
@ -157,7 +165,7 @@
(let [changes (-> (pcb/empty-changes it)
(pcb/add-token-set new-token-set))]
(rx/of
(set-selected-token-set-id (:name new-token-set))
(set-selected-token-set-id-from-name (:name new-token-set))
(dch/commit-changes changes)))))))
(defn update-token-set [set-name token-set]
@ -169,7 +177,7 @@
changes (-> (pcb/empty-changes it)
(pcb/update-token-set token-set prev-token-set))]
(rx/of
(set-selected-token-set-id (:name token-set))
(set-selected-token-set-id-from-name (:name token-set))
(dch/commit-changes changes))))))
(defn toggle-token-set [{:keys [token-set-name]}]
@ -202,7 +210,7 @@
(ctob/get-sets)
(first)
(:name)
(set-selected-token-set-id))
(set-selected-token-set-id-from-name))
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-tokens-lib lib))]
@ -211,14 +219,14 @@
update-token-set-change
(wtu/update-workspace-tokens))))))
(defn delete-token-set [token-set-name]
(ptk/reify ::delete-token-set
(defn delete-token-set-path [token-set-path]
(ptk/reify ::delete-token-set-path
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/delete-token-set token-set-name))]
(pcb/delete-token-set-path token-set-path))]
(rx/of
(dch/commit-changes changes)
(wtu/update-workspace-tokens))))))
@ -268,7 +276,7 @@
(pcb/update-token (pcb/empty-changes) (:name token-set) token prev-token)
(pcb/add-token (pcb/empty-changes) (:name token-set) token)))]
(rx/of
(set-selected-token-set-id token-set-name)
(set-selected-token-set-id-from-name token-set-name)
(dch/commit-changes changes))))))
(defn delete-token

View file

@ -493,9 +493,15 @@
(def workspace-selected-token-set-id
(l/derived wtts/get-selected-token-set-id st/state))
(def workspace-token-set-group-selected?
(l/derived wtts/token-group-selected? st/state))
(def workspace-ordered-token-sets
(l/derived #(or (some-> % ctob/get-sets) []) tokens-lib))
(def workspace-token-sets-tree
(l/derived (d/nilf ctob/get-set-tree) tokens-lib))
(def workspace-active-theme-paths
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))

View file

@ -51,7 +51,7 @@
(mf/defc color-bullet
{::mf/wrap [mf/memo]
::mf/wrap-props false}
[{:keys [color on-click mini? area]}]
[{:keys [color on-click mini area]}]
(let [read-only? (nil? on-click)
on-click
(mf/use-fn
@ -73,7 +73,7 @@
[:div
{:class (stl/css-case
:color-bullet true
:mini mini?
:mini mini
:is-library-color (some? id)
:is-not-library-color (nil? id)
:is-gradient (some? gradient)

View file

@ -8,6 +8,7 @@
// TODO: create actual tokens once we have them from design
$br-8: px2rem(8);
$br-4: px2rem(4);
$br-circle: 50%;
$b-1: px2rem(1);

View file

@ -83,7 +83,7 @@ $grayish-red: #bfbfbf;
--color-foreground-primary: #{$black};
--color-foreground-secondary: #{$blue-teal-700};
--color-shadow: #{color.change($blue-teal-700, $alpha: 0.2)};
--color-shadow-dark: #{color.change($gray-200, $alpha: 0.6)};
--color-overlay-default: #{$white-60};
--color-overlay-onboarding: #{$white-90};
--color-canvas: #{$grayish-red};
@ -115,7 +115,7 @@ $grayish-red: #bfbfbf;
--color-foreground-primary: #{$white};
--color-foreground-secondary: #{$grayish-blue-500};
--color-shadow: #{color.change($black, $alpha: 0.6)};
--color-shadow-dark: #{color.change($black, $alpha: 0.6)};
--color-overlay-default: #{$gray-950-60};
--color-overlay-onboarding: #{$gray-950-90};
--color-canvas: #{$grayish-red};

View file

@ -23,9 +23,10 @@
(mf/defc input*
{::mf/props :obj
::mf/forward-ref true
::mf/schema schema:input}
[{:keys [icon class type ref] :rest props}]
(let [ref (or ref (mf/use-ref))
[{:keys [icon class type external-ref] :rest props}]
(let [ref (or external-ref (mf/use-ref))
type (or type "text")
icon-class (stl/css-case :input true
:input-with-icon (some? icon))
@ -37,4 +38,4 @@
(dom/focus! input-node))))]
[:> "span" {:class (dm/str class " " (stl/css :container))}
(when icon [:> icon* {:id icon :class (stl/css :icon) :on-click handle-icon-click}])
[:> "input" props]]))
[:> "input" props]]))

View file

@ -18,6 +18,7 @@
column-gap: var(--sp-xs);
align-items: center;
position: relative;
inline-size: 100%;
background: var(--input-bg-color);
border-radius: $br-8;
@ -48,6 +49,7 @@
height: $sz-32;
border: none;
background: none;
inline-size: 100%;
@include use-typography("body-small");
color: var(--input-fg-color);

View file

@ -4,9 +4,9 @@
//
// Copyright (c) KALEIDOS INC
$elevation-shadow: 0 0 10px 0 var(--color-shadow);
$el-shadow-dark: 0 0 10px 0 var(--color-shadow-dark);
:global(.light),
:global(.default) {
--elevation-shadow: #{$elevation-shadow};
--el-shadow-dark: #{$el-shadow-dark};
}

View file

@ -32,6 +32,7 @@
[app.main.ui.workspace.sidebar.collapsable-button :refer [collapsed-button]]
[app.main.ui.workspace.sidebar.history :refer [history-toolbox]]
[app.main.ui.workspace.tokens.modals]
[app.main.ui.workspace.tokens.modals.themes]
[app.main.ui.workspace.viewport :refer [viewport]]
[app.util.debug :as dbg]
[app.util.dom :as dom]

View file

@ -10,7 +10,7 @@
background-color: rgba(var(--hue-rgb));
position: relative;
height: $s-140;
width: $s-256;
width: 100%;
margin-top: $s-12;
margin-bottom: $s-12;
cursor: pointer;
@ -47,5 +47,7 @@
}
.sliders-wrapper {
@include flexColumn;
display: flex;
flex-direction: column;
flex: 1;
}

View file

@ -51,27 +51,27 @@
value (+ min-value (* unit-value (- max-value min-value)))]
(on-change value))))]
[:div {:class (stl/css-case :opacity-wrapper (= type :opacity))}
[:div {:class (dm/str class (stl/css-case :vertical vertical?
:slider-selector true
:hue (= type :hue)
:opacity (= type :opacity)
:value (= type :value)))
:on-pointer-down handle-start-drag
:on-pointer-up handle-stop-drag
:on-lost-pointer-capture handle-stop-drag
:on-click calculate-pos
:on-pointer-move #(when @dragging? (calculate-pos %))}
(let [value-percent (* (/ (- value min-value)
(- max-value min-value)) 100)
value-percent (if reverse?
(mth/abs (- value-percent 100))
value-percent)
value-percent-str (str value-percent "%")
[:div {:class (dm/str class (stl/css-case :vertical vertical?
:slider-selector true
:hue (= type :hue)
:opacity (= type :opacity)
:value (= type :value)))
:on-pointer-down handle-start-drag
:on-pointer-up handle-stop-drag
:on-lost-pointer-capture handle-stop-drag
:on-click calculate-pos
:on-pointer-move #(when @dragging? (calculate-pos %))}
(let [value-percent (* (/ (- value min-value)
(- max-value min-value)) 100)
style-common #js {:pointerEvents "none"}
style-horizontal (obj/merge! #js {:left value-percent-str} style-common)
style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)]
[:div {:class (stl/css :handler)
:style (if vertical? style-vertical style-horizontal)}])]]))
value-percent (if reverse?
(mth/abs (- value-percent 100))
value-percent)
value-percent-str (str value-percent "%")
style-common #js {:pointerEvents "none"}
style-horizontal (obj/merge! #js {:left value-percent-str} style-common)
style-vertical (obj/merge! #js {:bottom value-percent-str} style-common)]
[:div {:class (stl/css :handler)
:style (if vertical? style-vertical style-horizontal)}])]))

View file

@ -17,7 +17,7 @@
position: relative;
align-self: center;
height: $s-24;
width: $s-200;
inline-size: 100%;
border: $s-2 solid var(--colorpicker-details-color);
border-radius: $br-6;
background: linear-gradient(

View file

@ -6,22 +6,6 @@
@import "refactor/common-refactor.scss";
.input {
@extend .input-element;
}
.labeled-input {
@extend .input-element;
.label {
width: auto;
text-wrap: nowrap;
}
}
.labeled-input-error {
border: 1px solid var(--status-color-error-500) !important;
}
.button {
@extend .button-primary;
}

View file

@ -0,0 +1,27 @@
;; 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.components.controls.input-token-color-bullet
(:require-macros [app.main.style :as stl])
(:require
[app.main.ui.components.color-bullet :refer [color-bullet]]
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
[rumext.v2 :as mf]))
(def ^:private schema::input-token-color-bullet
[:map
[:color [:maybe :string]]
[:on-click fn?]])
(mf/defc input-token-color-bullet*
{::mf/props :obj
::mf/schema schema::input-token-color-bullet}
[{:keys [color on-click]}]
[:div {:class (stl/css :input-token-color-bullet)
:on-click on-click}
(if-let [hex (some-> color tinycolor/valid-color tinycolor/->hex)]
[:> color-bullet {:color hex :mini true}]
[:div {:class (stl/css :input-token-color-bullet-placeholder)}])])

View file

@ -0,0 +1,28 @@
// 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
@use "../../../../ds/_sizes.scss" as *;
@use "../../../../ds/_borders.scss" as *;
.input-token-color-bullet {
--bullet-size: var(--sp-l);
--bullet-default-color: var(--color-foreground-secondary);
--bullet-radius: var(--br-4);
margin-inline-end: var(--sp-s);
cursor: pointer;
}
.input-token-color-bullet-placeholder {
width: var(--bullet-size, --sp-l);
height: var(--bullet-size, --sp-l);
min-width: var(--bullet-size, --sp-l);
min-height: var(--bullet-size, --sp-l);
margin-top: 0;
background-color: color-mix(in hsl, var(--bullet-default-color) 30%, transparent);
border-radius: $br-4;
cursor: pointer;
}

View file

@ -0,0 +1,43 @@
;; 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.components.controls.input-tokens
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.ui.ds.controls.input :refer [input*]]
[rumext.v2 :as mf]))
(def ^:private schema::input-tokens
[:map
[:id :string]
[:label :string]
[:placeholder {:optional true} :string]
[:default-value {:optional true} [:maybe :string]]
[:class {:optional true} :string]
[:error {:optional true} :boolean]
[:value {:optional true} :string]])
(mf/defc input-tokens*
{::mf/props :obj
::mf/forward-ref true
::mf/schema schema::input-tokens}
[{:keys [class label external-ref id error value children] :rest props}]
(let [ref (or external-ref (mf/use-ref))
props (mf/spread-props props {:id id
:type "text"
:class (stl/css :input)
:aria-invalid error
:value value
:external-ref ref})]
[:div {:class (dm/str class " "
(stl/css-case :wrapper true
:input-error error))}
[:label {:for id :class (stl/css :label)} label]
[:div {:class (stl/css :input-wrapper)}
(when children
[:div {:class (stl/css :input-swatch)} children])
[:> input* props]]]))

View file

@ -0,0 +1,53 @@
// 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
@use "../../../../ds/typography.scss" as *;
@use "../../../../ds/_borders.scss" as *;
@use "../../../../ds/_sizes.scss" as *;
@import "refactor/common-refactor.scss";
.wrapper {
--label-color: var(--color-foreground-primary);
--input-bg-color: var(--color-background-tertiary);
--input-fg-color: var(--color-foreground-primary);
--input-icon-color: var(--color-foreground-secondary);
--input-outline-color: none;
display: flex;
flex-direction: column;
gap: var(--sp-xs);
&.input-error {
--input-outline-color: var(--color-accent-error);
}
}
.label {
@include use-typography("body-small");
@include textEllipsis;
color: var(--label-color);
}
.input-wrapper {
display: flex;
align-items: center;
&:has(.input-swatch) {
position: relative;
& .input {
padding-inline-start: var(--sp-xxxl);
}
}
}
.input-swatch {
position: absolute;
display: flex;
inset-inline-start: var(--sp-s);
z-index: 2;
}

View file

@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.types.tokens-lib :as ctob]
[app.main.data.modal :as modal]
[app.main.data.tokens :as dt]
[app.main.refs :as refs]
@ -206,7 +207,7 @@
(defn default-actions [{:keys [token selected-token-set-id]}]
(let [{:keys [modal]} (wtty/get-token-properties token)]
[{:title "Delete Token"
:action #(st/emit! (dt/delete-token selected-token-set-id (:name token)))}
:action #(st/emit! (dt/delete-token (ctob/set-path->set-name selected-token-set-id) (:name token)))}
{:title "Duplicate Token"
:action #(st/emit! (dt/duplicate-token (:name token)))}
{:title "Edit Token"

View file

@ -15,18 +15,19 @@
[app.main.data.tokens :as dt]
[app.main.refs :as refs]
[app.main.store :as st]
[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]]
[app.main.ui.workspace.tokens.common :as tokens.common]
[app.main.ui.workspace.tokens.components.controls.input-token-color-bullet :refer [input-token-color-bullet*]]
[app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]]
[app.main.ui.workspace.tokens.errors :as wte]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.tinycolor :as tinycolor]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.dom :as dom]
[app.util.functions :as uf]
@ -205,6 +206,7 @@ Token names should only contain letters and digits separated by . characters.")}
{::mf/wrap-props false}
[{:keys [token token-type action selected-token-set-id]}]
(let [token (or token {:type token-type})
token-properties (wtty/get-token-properties token)
color? (wtt/color-token? token)
selected-set-tokens (mf/deref refs/workspace-selected-token-set-tokens)
active-theme-tokens (mf/deref refs/workspace-active-theme-sets-tokens)
@ -345,7 +347,7 @@ Token names should only contain letters and digits separated by . characters.")}
(fn [e]
(dom/prevent-default e)
(modal/hide!)
(st/emit! (dt/delete-token selected-token-set-id (:name token)))))
(st/emit! (dt/delete-token (ctob/set-path->set-name selected-token-set-id) (:name token)))))
on-cancel
(mf/use-fn
@ -362,14 +364,17 @@ Token names should only contain letters and digits separated by . characters.")}
(tr "workspace.token.create-token" token-type))]
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[:span {:class (stl/css :labeled-input-label)} "Name"]
[:& tokens.common/labeled-input {:label "Name"
:error? @name-errors
:input-props {:default-value @name-ref
:auto-focus true
:on-blur on-update-name
:on-change on-update-name}}]
(let [token-title (str/lower (:title token-properties))]
[:> input-tokens*
{:id "token-name"
:placeholder (tr "workspace.token.enter-token-name", token-title)
:error (boolean @name-errors)
:auto-focus true
:label (tr "workspace.token.token-name")
:default-value @name-ref
:on-blur on-update-name
:on-change on-update-name}])
(for [error (->> (:errors @name-errors)
(map #(-> (assoc @name-errors :errors [%])
(me/humanize))))]
@ -380,34 +385,31 @@ Token names should only contain letters and digits separated by . characters.")}
error])]
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[: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
:on-change on-update-value
:ref value-input-ref}
:render-right (when color?
(mf/fnc drop-down-button []
[:div {:class (stl/css :color-bullet)
:on-click #(swap! color-ramp-open? not)}
(if-let [hex (some-> @color tinycolor/valid-color tinycolor/->hex)]
[:& color-bullet {:color hex
:mini? true}]
[:div {:class (stl/css :color-bullet-placeholder)}])]))}]
[:> input-tokens*
{:id "token-value"
:placeholder (tr "workspace.token.enter-token-value")
:label (tr "workspace.token.token-value")
:default-value @value-ref
:external-ref value-input-ref
:on-change on-update-value
:on-blur on-update-value}
(when color?
[:> input-token-color-bullet*
{:color @color :on-click #(swap! color-ramp-open? not)}])]
(when @color-ramp-open?
[:& ramp {:color (some-> (or @token-resolve-result (:value token))
(tinycolor/valid-color))
:on-change on-update-color}])
[:& token-value-or-errors {:result-or-errors @token-resolve-result}]]
[:div {:class (stl/css :input-row)}
;; This should be remove when labeled-imput is modified
[: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}}]
[:> input-tokens*
{:id "token-description"
:placeholder (tr "workspace.token.enter-token-description")
:label (tr "workspace.token.token-description")
:default-value @description-ref
:on-blur on-update-description
:on-change on-update-description}]
(when @description-errors
[:> text* {:as "p"
:typography "body-small"

View file

@ -41,6 +41,7 @@
.labeled-input-label {
color: var(--color-foreground-primary);
font-size: $fs-12;
}
.error {
@ -64,22 +65,6 @@
--input-hint-color: var(--status-color-error-500);
}
.color-bullet {
margin-right: $s-8;
cursor: pointer;
}
.color-bullet-placeholder {
width: var(--bullet-size, $s-16);
height: var(--bullet-size, $s-16);
min-width: var(--bullet-size, $s-16);
min-height: var(--bullet-size, $s-16);
margin-top: 0;
background-color: color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent);
border-radius: $br-4;
cursor: pointer;
}
.form-modal-title {
color: var(--color-foreground-primary);
}

View file

@ -254,7 +254,7 @@
[{:keys [state set-state]}]
(let [{:keys [theme-path]} @state
[_ theme-group theme-name] theme-path
token-sets (mf/deref refs/workspace-ordered-token-sets)
token-sets (mf/deref refs/workspace-token-sets-tree)
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)] %))

View file

@ -160,7 +160,7 @@
.edit-theme-wrapper {
display: flex;
flex-direction: column;
gap: $s-12;
gap: $s-24;
}
.sets-list-wrapper {

View file

@ -7,14 +7,13 @@
(ns app.main.ui.workspace.tokens.sets
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.notifications :as ntf]
[app.common.types.tokens-lib :as ctob]
[app.main.data.tokens :as wdt]
[app.main.refs :as refs]
[app.main.store :as st]
[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.text :refer [text*]]
[app.main.ui.hooks :as h]
[app.main.ui.workspace.tokens.sets-context :as sets-context]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
@ -25,16 +24,16 @@
(defn on-toggle-token-set-click [token-set-name]
(st/emit! (wdt/toggle-token-set {:token-set-name token-set-name})))
(defn on-select-token-set-click [name]
(st/emit! (wdt/set-selected-token-set-id name)))
(defn on-select-token-set-click [tree-path]
(st/emit! (wdt/set-selected-token-set-id tree-path)))
(defn on-update-token-set [set-name token-set]
(st/emit! (wdt/update-token-set set-name token-set)))
(defn on-create-token-set [token-set]
(defn on-create-token-set [_ token-set]
(st/emit! (wdt/create-token-set token-set)))
(mf/defc editing-node
(mf/defc editing-label
[{:keys [default-value on-cancel on-submit]}]
(let [ref (mf/use-ref)
on-submit-valid (mf/use-fn
@ -43,7 +42,9 @@
(if (or (str/empty? value)
(= value default-value))
(on-cancel)
(on-submit value)))))
(do
(on-submit value)
(on-cancel))))))
on-key-down (mf/use-fn
(fn [event]
(cond
@ -58,135 +59,167 @@
:auto-focus true
:default-value default-value}]))
(mf/defc sets-tree
[{:keys [token-set
token-set-active?
token-set-selected?
editing?
on-select
on-toggle
on-edit
on-submit
on-cancel]
:as _props}]
(let [{:keys [name _children]} token-set
selected? (and set? (token-set-selected? name))
visible? (token-set-active? name)
collapsed? (mf/use-state false)
set? true #_(= type :set)
group? false #_(= type :group)
editing-node? (editing? name)
(mf/defc sets-tree-set-group
[{:keys [label tree-depth tree-path selected? collapsed? on-select editing? on-edit on-edit-reset on-edit-submit]}]
(let [editing?' (editing? tree-path)
on-click
(mf/use-fn
(mf/deps editing-node?)
(mf/deps editing? tree-path)
(fn [event]
(dom/stop-propagation event)
(when-not editing-node?
(on-select name))))
(when-not (editing? tree-path)
(on-select tree-path))))
on-context-menu
(mf/use-fn
(mf/deps editing-node? name)
(mf/deps editing? tree-path)
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(when-not editing-node?
(when-not (editing? tree-path)
(st/emit!
(wdt/show-token-set-context-menu
{:position (dom/get-client-position event)
:token-set-name name})))))
on-drag
(mf/use-fn
(mf/deps name)
(fn [_]
(when-not selected?
(on-select name))))
on-drop
(mf/use-fn
(mf/deps name)
(fn [position data]
(st/emit! (wdt/move-token-set (:name data) name position))))
on-submit-edit
(mf/use-fn
(mf/deps on-submit token-set)
#(on-submit (assoc token-set :name %)))
on-edit-name
(mf/use-fn
(fn [e]
(let [name (-> (dom/get-current-target e)
(dom/get-data "name"))]
(on-edit name))))
on-toggle-set (fn [event]
(dom/stop-propagation event)
(on-toggle name))
on-collapse (mf/use-fn #(swap! collapsed? not))
[dprops dref]
(h/use-sortable
:data-type "penpot/token-set"
:on-drag on-drag
:on-drop on-drop
:data {:name name}
:draggable? true)]
[:div {:ref dref
:tree-path tree-path})))))]
[:div {;; :ref dref
:role "button"
:style {"--tree-depth" tree-depth}
:class (stl/css-case :set-item-container true
:dnd-over (= (:over dprops) :center)
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))
:selected-set selected?)
:on-click on-click
:on-double-click on-edit-name
:on-context-menu on-context-menu
:data-name name}
[:div {:class (stl/css-case :set-item-group group?
:set-item-set set?
:selected-set selected?)}
(when group?
[:> icon-button* {:on-click on-collapse
:aria-label (tr "labels.collapse")
:icon (if @collapsed?
"arrow-right"
"arrow-down")
:variant "action"}])
:on-double-click #(on-edit tree-path)}
[:> icon-button*
{:on-click (fn [event]
(.stopPropagation event)
(swap! collapsed? not))
:aria-label (tr "labels.collapse")
:icon (if @collapsed? "arrow-right" "arrow-down")
:variant "action"}]
[:> icon*
{:id "group"
:class (stl/css :icon)}]
(if editing?'
[:& editing-label
{:default-value label
:on-cancel on-edit-reset
:on-create on-edit-reset
:on-submit #(on-edit-submit)}]
[:div {:class (stl/css :set-name)} label])]))
[:> icon* {:id (if set? "document" "group")
:class (stl/css :icon)}]
(if editing-node?
[:& editing-node {:default-value name
:on-submit on-submit-edit
:on-cancel on-cancel}]
[:*
[:div {:class (stl/css :set-name)} name]
(if set?
[:button {:on-click on-toggle-set
:class (stl/css-case :checkbox-style true
:checkbox-checked-style visible?)}
(when visible?
[:> icon* {:aria-label (tr "workspace.token.select-set")
:class (stl/css :check-icon)
:size "s"
:id ic/tick}])]
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 sets-tree-set
[{:keys [set label tree-depth tree-path selected? on-select active? on-toggle editing? on-edit on-edit-reset on-edit-submit]}]
(let [set-name (.-name set)
editing?' (editing? tree-path)
active?' (active? set-name)
on-click
(mf/use-fn
(mf/deps editing?' tree-path)
(fn [event]
(dom/stop-propagation event)
(when-not editing?'
(on-select tree-path))))
(defn warn-on-try-create-token-set-group! []
(st/emit! (ntf/show {:content (tr "workspace.token.grouping-set-alert")
:notification-type :toast
:type :warning
:timeout 3000})))
on-context-menu
(mf/use-fn
(mf/deps editing?' tree-path)
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(when-not editing?'
(st/emit!
(wdt/show-token-set-context-menu
{:position (dom/get-client-position event)
:tree-path tree-path})))))]
[:div {;; :ref dref
:role "button"
:style {"--tree-depth" tree-depth}
:class (stl/css-case :set-item-container true
:selected-set selected?)
:on-click on-click
:on-double-click #(on-edit tree-path)
:on-context-menu on-context-menu}
[:> icon*
{:id "document"
:class (stl/css-case :icon true
:root-icon (not tree-depth))}]
(if editing?'
[:& editing-label
{:default-value label
:on-cancel on-edit-reset
:on-create on-edit-reset
:on-submit #(on-edit-submit set-name (ctob/update-name set %))}]
[:*
[:div {:class (stl/css :set-name)} label]
[:button {:on-click (fn [event]
(dom/stop-propagation event)
(on-toggle set-name))
:class (stl/css-case :checkbox-style true
:checkbox-checked-style active?')}
(when active?'
[:> icon* {:aria-label (tr "workspace.token.select-set")
:class (stl/css :check-icon)
:size "s"
:id ic/tick}])]])]))
(mf/defc sets-tree
[{:keys [set-path set-node tree-depth tree-path on-select selected? on-toggle active? editing? on-edit on-edit-reset on-edit-submit]
:or {tree-depth 0}
:as props}]
(let [[set-prefix set-path'] (some-> set-path (ctob/split-set-prefix))
set? (instance? ctob/TokenSet set-node)
set-group? (= ctob/set-group-prefix set-prefix)
root? (= tree-depth 0)
collapsed? (mf/use-state false)
children? (and
(or root? set-group?)
(not @collapsed?))]
[:*
(cond
root? nil
set?
[:& sets-tree-set
{:set set-node
:active? active?
:selected? (selected? tree-path)
:on-select on-select
:label set-path'
:tree-path (or tree-path set-path)
:tree-depth tree-depth
:editing? editing?
:on-toggle on-toggle
:on-edit on-edit
:on-edit-reset on-edit-reset
:on-edit-submit on-edit-submit}]
set-group?
[:& sets-tree-set-group
{:selected? (selected? tree-path)
:on-select on-select
:label set-path'
:collapsed? collapsed?
:tree-path (or tree-path set-path)
:tree-depth tree-depth
:editing? editing?
:on-edit on-edit
:on-edit-reset on-edit-reset
:on-edit-submit on-edit-submit}])
(when children?
(for [[set-path set-node] set-node
:let [tree-path' (str (when tree-path (str tree-path "/")) set-path)]]
[:& sets-tree
{:key tree-path'
:set-path set-path
:set-node set-node
:tree-depth (when-not root? (inc tree-depth))
:tree-path tree-path'
:on-select on-select
:selected? selected?
:on-toggle on-toggle
:active? active?
:editing? editing?
:on-edit on-edit
:on-edit-reset on-edit-reset
:on-edit-submit on-edit-submit}]))]))
(mf/defc controlled-sets-list
[{:keys [token-sets
@ -199,66 +232,53 @@
on-select
context]
:as _props}]
(let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context))
avoid-token-set-grouping #(str/replace % "/" "-")
submit-token
#(do
;; TODO: We don't support set grouping for now so we rename sets for now
(when (str/includes? (:name %) "/")
(warn-on-try-create-token-set-group!))
(on-create-token-set (update % :name avoid-token-set-grouping))
(on-reset))]
(let [{:keys [editing? new? on-edit on-reset] :as ctx} (or context (sets-context/use-context))]
[:ul {:class (stl/css :sets-list)}
(if (and
(= origin "theme-modal")
(empty? token-sets))
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")]
(for [token-set token-sets]
(when token-set
(let [update-token
#(do
;; TODO: We don't support set grouping for now so we rename sets for now
(when (str/includes? (:name %) "/")
(warn-on-try-create-token-set-group!))
(on-update-token-set (avoid-token-set-grouping (:name token-set)) (update % :name avoid-token-set-grouping))
(on-reset))]
[:& sets-tree
{:key (:name token-set)
: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 update-token
: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 submit-token
:on-cancel on-reset}])]))
(if (and (= origin "theme-modal")
(empty? token-sets))
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")]
[:*
[:& sets-tree
{:set-node token-sets
:selected? token-set-selected?
:on-select on-select
:active? token-set-active?
:on-toggle on-toggle-token-set
:editing? editing?
:on-edit on-edit
:on-edit-reset on-reset
:on-edit-submit on-update-token-set}]
(when new?
[:& sets-tree-set
{:set (ctob/make-token-set :name "")
:label ""
:selected? (constantly true)
:active? (constantly true)
:editing? (constantly true)
:on-select (constantly nil)
:on-edit (constantly nil)
:on-edit-reset on-reset
:on-edit-submit on-create-token-set}])]))]))
(mf/defc sets-list
[{:keys []}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
(let [token-sets (mf/deref refs/workspace-token-sets-tree)
selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)
token-set-selected? (mf/use-fn
(mf/deps token-sets selected-token-set-id)
(fn [set-name]
(= set-name selected-token-set-id)))
active-token-set-ids (mf/deref refs/workspace-active-set-names)
(fn [tree-path]
(= tree-path selected-token-set-id)))
active-token-set-names (mf/deref refs/workspace-active-set-names)
token-set-active? (mf/use-fn
(mf/deps active-token-set-ids)
(fn [id]
(get active-token-set-ids id)))]
(mf/deps active-token-set-names)
(fn [set-name]
(get active-token-set-names set-name)))]
[:& controlled-sets-list
{:token-sets token-sets
:token-set-selected? token-set-selected?

View file

@ -13,10 +13,14 @@
}
.set-item-container {
@include bodySmallTypography;
display: flex;
align-items: center;
width: 100%;
min-height: $s-32;
cursor: pointer;
color: var(--layer-row-foreground-color);
padding-left: $s-20;
padding-left: calc($s-32 * var(--tree-depth, 0));
border: $s-2 solid transparent;
&.dnd-over-bot {
@ -30,17 +34,6 @@
}
}
.set-item-set,
.set-item-group {
@include bodySmallTypography;
display: flex;
align-items: center;
min-height: $s-32;
width: 100%;
cursor: pointer;
color: var(--layer-row-foreground-color);
}
.set-name {
@include textEllipsis;
flex-grow: 1;
@ -55,6 +48,10 @@
padding-right: $s-4;
}
.root-icon {
margin-left: $s-8;
}
.checkbox-style {
display: flex;
justify-content: center;
@ -76,7 +73,7 @@
color: var(--color-background-secondary);
}
.set-item-set:hover {
.set-item-container:hover {
background-color: var(--layer-row-background-color-hover);
color: var(--layer-row-foreground-color-hover);
box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover);

View file

@ -35,10 +35,10 @@
[:span {:class (stl/css :title)} title]])
(mf/defc menu
[{:keys [token-set-name]}]
[{:keys [tree-path]}]
(let [{:keys [on-edit]} (sets-context/use-context)
edit-name (mf/use-fn #(on-edit token-set-name))
delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set token-set-name)))]
edit-name (mf/use-fn #(on-edit tree-path))
delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set-path tree-path)))]
[:ul {:class (stl/css :context-list)}
[:& menu-entry {:title (tr "labels.rename") :on-click edit-name}]
[:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]]))
@ -49,8 +49,7 @@
top (+ (get-in mdata [:position :y]) 5)
left (+ (get-in mdata [:position :x]) 5)
width (mf/use-state 0)
dropdown-ref (mf/use-ref)
token-set-name (:token-set-name mdata)]
dropdown-ref (mf/use-ref)]
(mf/use-effect
(mf/deps mdata)
(fn []
@ -62,4 +61,4 @@
:ref dropdown-ref
:style {:top top :left left}
:on-context-menu prevent-default}
[:& menu {:token-set-name token-set-name}]]]))
[:& menu {:tree-path (:tree-path mdata)}]]]))

View file

@ -195,51 +195,59 @@
[:div {:class (stl/css :theme-select-wrapper)}
[:& theme-select]
[:> button* {:variant "secondary"
:class (stl/css :edit-theme-button)
:on-click open-modal}
(tr "labels.edit")]])]))
(mf/defc add-set-button
[{:keys [on-open style]}]
(let [{:keys [on-create]} (sets-context/use-context)
(let [{:keys [on-create new?]} (sets-context/use-context)
on-click #(do
(on-open)
(on-create))]
(if (= style "inline")
[:button {:on-click on-click
:class (stl/css :create-theme-button)}
(tr "workspace.token.create-one")]
(when-not new?
[:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.token.no-sets-yet")]
[:button {:on-click on-click
:class (stl/css :create-theme-button)}
(tr "workspace.token.create-one")]])
[:> icon-button* {:variant "ghost"
:icon "add"
:on-click on-click
:aria-label (tr "workspace.token.add set")}])))
(mf/defc themes-sets-tab
[]
(mf/defc theme-sets-list
[{:keys [on-open]}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets)
open? (mf/use-state true)
{:keys [new?] :as ctx} (sets-context/use-context)]
(if (and (empty? token-sets)
(not new?))
[:& add-set-button {:on-open on-open
:style "inline"}]
[:& h/sortable-container {}
[:& sets-list]])))
(mf/defc themes-sets-tab
[{:keys [resize-height]}]
(let [open? (mf/use-state true)
on-open (mf/use-fn #(reset! open? true))]
[:& sets-context/provider {}
[:& sets-context-menu]
[:div {:class (stl/css :sets-sidebar)}
[:& themes-header]
[:div {:class (stl/css :sidebar-header)}
[:& title-bar {:collapsable true
:collapsed (not @open?)
:all-clickable true
:title (tr "labels.sets")
:on-collapsed #(swap! open? not)}
[:& add-set-button {:on-open on-open
:style "header"}]]]
(when @open?
[:& h/sortable-container {}
[:*
(when (empty? token-sets)
[:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.token.no-sets-yet")]
[:& add-set-button {:on-open on-open
:style "inline"}]])
[:& sets-list]]])]]))
[:article {:class (stl/css :sets-section-wrapper)
:style {"--resize-height" (str resize-height "px")}}
[:div {:class (stl/css :sets-sidebar)}
[:& themes-header]
[:div {:class (stl/css :sidebar-header)}
[:& title-bar {:collapsable true
:collapsed (not @open?)
:all-clickable true
:title (tr "labels.sets")
:on-collapsed #(swap! open? not)}
[:& add-set-button {:on-open on-open
:style "header"}]]]
[:& theme-sets-list {:on-open on-open}]]]]))
(mf/defc tokens-tab
[_props]
@ -348,13 +356,11 @@
size-pages-opened :size}
(use-resize-hook :tokens 200 38 400 :y false nil)]
[:div {:class (stl/css :sidebar-wrapper)}
[:article {:class (stl/css :sets-section-wrapper)
:style {"--resize-height" (str size-pages-opened "px")}}
[:& themes-sets-tab]]
[:& themes-sets-tab {:resize-height size-pages-opened}]
[:article {:class (stl/css :tokens-section-wrapper)}
[:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages
:on-lost-pointer-capture on-lost-pointer-capture-pages
:on-pointer-move on-pointer-move-pages}]
[:& tokens-tab]
[:& import-export-button]]]))
[:& tokens-tab]]
[:& import-export-button]]))

View file

@ -10,10 +10,10 @@
.sidebar-wrapper {
display: grid;
grid-template-rows: auto auto 1fr;
grid-template-rows: auto 1fr auto;
// Overflow on the bottom section can't be done without hardcoded values for the height
// This has to be changed from the wrapping sidebar styles
height: calc(100vh - #{$s-84});
height: calc(100vh - #{$s-92});
overflow: hidden;
}
@ -114,9 +114,15 @@
}
.import-export-button-wrapper {
position: absolute;
bottom: $s-12;
right: $s-12;
position: relative;
display: flex;
flex-direction: row;
align-items: end;
justify-content: end;
padding: $s-16;
margin-top: $s-8;
background-color: var(--color-background-primary);
box-shadow: var(--el-shadow-dark);
}
.import-export-button {
@ -187,6 +193,10 @@
cursor: pointer;
}
.edit-theme-button {
justify-content: center;
}
.resize-area-horiz {
position: absolute;
left: 0;

View file

@ -182,8 +182,7 @@
(->> data-stream
(rx/map (fn [data]
(try
(-> (str/replace data "/" "-") ;; TODO Remove when token groups work
(t/decode-str))
(t/decode-str data)
(catch js/Error e
(throw (wte/error-ex-info :error.import/json-parse-error data e))))))
(rx/map (fn [json-data]

View file

@ -41,16 +41,30 @@
(some-> (get-workspace-tokens-lib state)
(ctob/get-sets)
(first)
(:name))))
(ctob/get-set-path))))
(defn get-selected-token-set-node [state]
(when-let [path (some-> (get-selected-token-set-id state)
(ctob/split-token-set-path))]
(some-> (get-workspace-tokens-lib state)
(ctob/get-in-set-tree path))))
(defn get-selected-token-set [state]
(when-let [id (get-selected-token-set-id state)]
(some-> (get-workspace-tokens-lib state)
(ctob/get-set id))))
(let [set-node (get-selected-token-set-node state)]
(when (instance? ctob/TokenSet set-node)
set-node)))
(defn get-selected-token-set-group [state]
(let [set-node (get-selected-token-set-node state)]
(when (and set-node (not (instance? ctob/TokenSet set-node)))
set-node)))
(defn get-selected-token-set-tokens [state]
(some-> (get-selected-token-set state)
:tokens))
(defn token-group-selected? [state]
(some? (get-selected-token-set-group state)))
(defn assoc-selected-token-set-id [state id]
(assoc-in state [:workspace-local :selected-token-set-id] id))