♻️ Replace measure inputs for numeric input component

This commit is contained in:
Eva Marco
2025-09-10 13:03:13 +02:00
parent 07904bcc5d
commit d7d2d36e0a
10 changed files with 209 additions and 223 deletions

View File

@@ -51,6 +51,7 @@
"styles/v2"
"layout/grid"
"plugins/runtime"
"tokens/numeric-input"
"design-tokens/v1"
"text-editor/v2"
"render-wasm/v1"
@@ -80,6 +81,7 @@
#{"styles/v2"
"plugins/runtime"
"text-editor/v2"
"tokens/numeric-input"
"render-wasm/v1"})
;; Features that are mainly backend only or there are a proper
@@ -97,6 +99,7 @@
"design-tokens/v1"
"fdata/shape-data-type"
"fdata/path-data"
"tokens/numeric-input"
"variants/v1"}
(into frontend-only-features)
(into backend-only-features)))
@@ -121,6 +124,7 @@
:feature-text-editor-v2 "text-editor/v2"
:feature-render-wasm "render-wasm/v1"
:feature-variants "variants/v1"
:feature-token-input "tokens/numeric-input"
nil))
(defn migrate-legacy-features

View File

@@ -477,7 +477,7 @@
:max-height #{:sizing :dimensions}
:x #{:spacing :dimensions}
:y #{:spacing :dimensions}
:rotation #{:number}
:rotation #{:number :rotation}
:border-radius #{:border-radius :dimensions}
:row-gap #{:spacing :dimensions}
:column-gap #{:spacing :dimensions}

View File

@@ -40,137 +40,6 @@
(declare token-properties)
(declare update-layout-item-margin)
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
(defn apply-token
"Apply `attributes` that match `token` for `shape-ids`.
Optionally remove attributes from `attributes-to-remove`,
this is useful for applying a single attribute from an attributes set
while removing other applied tokens from this set."
[{:keys [attributes attributes-to-remove token shape-ids on-update-shape]}]
(ptk/reify ::apply-token
ptk/WatchEvent
(watch [_ state _]
;; We do not allow to apply tokens while text editor is open.
(when (empty? (get state :workspace-editor-state))
(let [attributes-to-remove
;; Remove atomic typography tokens when applying composite and vice-verca
(cond
(ctt/typography-token-keys (:type token)) (set/union attributes-to-remove ctt/typography-keys)
(ctt/typography-keys (:type token)) (set/union attributes-to-remove ctt/typography-token-keys)
:else attributes-to-remove)]
(when-let [tokens (some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-tokens-in-active-sets))]
(->> (sd/resolve-tokens tokens)
(rx/mapcat
(fn [resolved-tokens]
(let [undo-id (js/Symbol)
objects (dsh/lookup-page-objects state)
selected-shapes (select-keys objects shape-ids)
shape-ids (or (->> selected-shapes
(filter (fn [[_ shape]]
(or
(and (ctsl/any-layout-immediate-child? objects shape)
(some ctt/spacing-margin-keys attributes))
(ctt/any-appliable-attr? attributes (:type shape)))))
(keys))
[])
resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value])
tokenized-attributes (cft/attributes-map attributes token)
type (:type token)]
(rx/concat
(rx/of
(st/emit! (ev/event {::ev/name "apply-tokens"
:type type
:applyed-to attributes}))
(dwu/start-undo-transaction undo-id)
(dwsh/update-shapes shape-ids (fn [shape]
(cond-> shape
attributes-to-remove
(update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
:always
(update :applied-tokens merge tokenized-attributes)))))
(when on-update-shape
(let [res (on-update-shape resolved-value shape-ids attributes)]
;; Composed updates return observables and need to be executed differently
(if (rx/observable? res)
res
(rx/of res))))
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))))
(defn apply-spacing-token
"Handles edge-case for spacing token when applying token via toggle button.
Splits out `shape-ids` into seperate default actions:
- Layouts take the `default` update function
- Shapes inside layout will only take margin"
[{:keys [token shapes]}]
(ptk/reify ::apply-spacing-token
ptk/WatchEvent
(watch [_ state _]
(let [objects (dsh/lookup-page-objects state)
{:keys [attributes on-update-shape]}
(get token-properties (:type token))
{:keys [other frame-children]}
(group-by #(if (ctsl/any-layout-immediate-child? objects %) :frame-children :other) shapes)]
(rx/of
(apply-token {:attributes attributes
:token token
:shape-ids (map :id other)
:on-update-shape on-update-shape})
(apply-token {:attributes ctt/spacing-margin-keys
:token token
:shape-ids (map :id frame-children)
:on-update-shape update-layout-item-margin}))))))
(defn unapply-token
"Removes `attributes` that match `token` for `shape-ids`.
Doesn't update shape attributes."
[{:keys [attributes token shape-ids] :as _props}]
(ptk/reify ::unapply-token
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(let [remove-token #(when % (cft/remove-attributes-for-token attributes token %))]
(dwsh/update-shapes
shape-ids
(fn [shape]
(update shape :applied-tokens remove-token))))))))
(defn toggle-token
[{:keys [token shapes attrs]}]
(ptk/reify ::on-toggle-token
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [attributes all-attributes on-update-shape]}
(get token-properties (:type token))
unapply-tokens?
(cft/shapes-token-applied? token shapes (or all-attributes attributes))
shape-ids (map :id shapes)]
(if unapply-tokens?
(rx/of
(unapply-token {:attributes (or attrs all-attributes attributes)
:token token
:shape-ids shape-ids}))
(rx/of
(case (:type token)
:spacing
(apply-spacing-token {:token token
:shapes shapes})
(apply-token {:attributes (or attrs attributes)
:token token
:shape-ids shape-ids
:on-update-shape on-update-shape}))))))))
;; Events to update the value of attributes with applied tokens ---------------------------------------------------------
;; (note that dwsh/update-shapes function returns an event)
@@ -652,7 +521,7 @@
Splits out `shape-ids` into seperate default actions:
- Layouts take the `default` update function
- Shapes inside layout will only take margin"
[{:keys [token shapes]}]
[{:keys [token shapes attr]}]
(ptk/reify ::apply-spacing-token
ptk/WatchEvent
(watch [_ state _]
@@ -665,7 +534,7 @@
(group-by #(if (ctsl/any-layout-immediate-child? objects %) :frame-children :other) shapes)]
(rx/of
(apply-token {:attributes attributes
(apply-token {:attributes (or attr attributes)
:token token
:shape-ids (map :id other)
:on-update-shape on-update-shape})
@@ -690,13 +559,12 @@
(update shape :applied-tokens remove-token))))))))
(defn toggle-token
[{:keys [token shapes]}]
[{:keys [token shapes attrs]}]
(ptk/reify ::on-toggle-token
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [attributes all-attributes on-update-shape]}
(get token-properties (:type token))
unapply-tokens?
(cft/shapes-token-applied? token shapes (or all-attributes attributes))
@@ -710,8 +578,9 @@
(case (:type token)
:spacing
(apply-spacing-token {:token token
:attr attrs
:shapes shapes})
(apply-token {:attributes attributes
(apply-token {:attributes (or attrs attributes)
:token token
:shape-ids shape-ids
:on-update-shape on-update-shape}))))))))

View File

@@ -24,6 +24,7 @@
(def libraries (mf/create-context nil))
(def design-tokens (mf/create-context nil))
(def token-inputs (mf/create-context nil))
(def current-scroll (mf/create-context nil))
(def current-zoom (mf/create-context nil))

View File

@@ -158,16 +158,17 @@
[:class {:optional true} :string]
[:value {:optional true} [:maybe [:or
:int
:float
:string
[:= :multiple]]]]
[:default {:optional true} [:maybe :string]]
[:placeholder {:optional true} :string]
[:icon {:optional true} [:maybe schema:icon]]
[:disabled {:optional true} [:maybe :boolean]]
[:min {:optional true} [:maybe :int]]
[:max {:optional true} [:maybe :int]]
[:min {:optional true} [:maybe [:or :int :float]]]
[:max {:optional true} [:maybe [:or :int :float]]]
[:max-length {:optional true} :int]
[:step {:optional true} [:maybe :int]]
[:step {:optional true} [:maybe [:or :int :float]]]
[:is-selected-on-focus {:optional true} :boolean]
[:nillable {:optional true} :boolean]
[:applied-token {:optional true} [:maybe [:or :string [:= :multiple]]]]
@@ -177,7 +178,7 @@
[:on-focus {:optional true} fn?]
[:on-detach {:optional true} fn?]
[:property {:optional true} :string]
[:align {:optional true} [:enum :left :right]]])
[:align {:optional true} [:maybe [:enum :left :right]]]])
(mf/defc numeric-input*
{::mf/schema schema:numeric-input}
@@ -195,7 +196,6 @@
tokens (if (object? tokens)
(mfu/bean tokens)
tokens)
value (if (= :multiple applied-token)
:multiple
value)

View File

@@ -17,6 +17,7 @@
position: absolute;
top: $sz-36;
width: var(--dropdown-width, 100%);
transform: translateX(var(--dropdown-translate-distance, 0));
background-color: var(--options-dropdown-bg-color);
border-radius: $br-8;
border: $b-1 solid var(--options-dropdown-border-color);

View File

@@ -25,7 +25,7 @@
inline-size: 100%;
background: var(--token-field-bg-color);
border-radius: $br-8;
padding: var(--sp-s);
padding: var(--sp-xs) var(--sp-s);
outline: $b-1 solid var(--token-field-outline-color);
&:hover {

View File

@@ -303,7 +303,6 @@
[:> (mf/provider muc/sidebar) {:value :right}
[:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type}
[:aside
{:class (stl/css-case :right-settings-bar true
:not-expand (not can-be-expanded?)

View File

@@ -5,7 +5,7 @@
[app.common.types.shape.radius :as ctsr]
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.numeric-input :as deprecated-input]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.hooks :as hooks]
@@ -68,6 +68,7 @@
(change-radius (fn [shape]
(ctsr/set-radius-to-all-corners shape value))))))
on-radius-4-change
(mf/use-fn
(mf/deps ids change-radius)
@@ -98,7 +99,7 @@
[:> icon* {:icon-id i/corner-radius
:size "s"
:class (stl/css :icon)}]
[:> numeric-input*
[:> deprecated-input/numeric-input*
{:placeholder (cond
(not all-equal?)
"Mixed"
@@ -113,7 +114,7 @@
[:div {:class (stl/css :radius-4)}
[:div {:class (stl/css :small-input)}
[:> numeric-input*
[:> deprecated-input/numeric-input*
{:placeholder "--"
:title (tr "workspace.options.radius-top-left")
:min 0
@@ -121,7 +122,7 @@
:value (:r1 values)}]]
[:div {:class (stl/css :small-input)}
[:> numeric-input*
[:> deprecated-input/numeric-input*
{:placeholder "--"
:title (tr "workspace.options.radius-top-right")
:min 0
@@ -129,7 +130,7 @@
:value (:r2 values)}]]
[:div {:class (stl/css :small-input)}
[:> numeric-input*
[:> deprecated-input/numeric-input*
{:placeholder "--"
:title (tr "workspace.options.radius-bottom-left")
:min 0
@@ -137,7 +138,7 @@
:value (:r4 values)}]]
[:div {:class (stl/css :small-input)}
[:> numeric-input*
[:> deprecated-input/numeric-input*
{:placeholder "--"
:title (tr "workspace.options.radius-bottom-right")
:min 0

View File

@@ -22,14 +22,15 @@
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.numeric-input :as deprecated-input]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.context :as muc]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.numeric-input :as ni]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.sidebar.options.menus.border-radius :refer [border-radius-menu*]]
@@ -93,13 +94,17 @@
(mf/defc numeric-input-wrapper*
{::mf/private true}
[{:keys [values name applied-tokens] :rest props}]
[{:keys [values name applied-tokens align on-detach] :rest props}]
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
tokens (mf/with-memo [tokens name]
(delay
(-> (deref tokens)
(select-keys (get tk/tokens-by-input name))
(not-empty))))
on-detach-attr
(mf/use-fn
(mf/deps on-detach name)
#(on-detach % name))
props (mf/spread-props props
{:placeholder (if (or (= :multiple (:applied-tokens values))
@@ -108,15 +113,20 @@
:class (stl/css :numeric-input-measures)
:applied-token (get applied-tokens name)
:tokens tokens
:align align
:on-detach on-detach-attr
:value (get values name)})]
[:> ni/numeric-input* props]))
[:> numeric-input* props]))
(def ^:private xf:map-type (map :type))
(def ^:private xf:mapcat-type-to-options (mapcat type->options))
(mf/defc measures-menu*
[{:keys [ids values applied-tokens type shapes]}]
(let [all-types
(let [token-numeric-inputs
(features/use-feature "tokens/numeric-input")
all-types
(mf/with-memo [type shapes]
;; We only need this when multiple type is used
(when (= type :multiple)
@@ -281,8 +291,7 @@
(st/emit! (udw/change-orientation ids (keyword orientation)))))
;; SIZE AND PROPORTION LOCK
on-size-change
do-size-change
(mf/use-fn
(mf/deps ids)
(fn [value attr]
@@ -290,6 +299,24 @@
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(udw/update-dimensions ids attr value)))))
on-size-change
(mf/use-fn
(mf/deps ids)
(fn [value attr]
(if (or (string? value) (int? value))
(do
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(binding [cts/*wasm-sync* true]
(run! #(do-size-change value attr) shapes)))
(do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value)
:attrs #{attr}
:shapes shapes}))
(binding [cts/*wasm-sync* true]
(run! #(do-size-change resolved-value attr) shapes)))))))
on-proportion-lock-change
(mf/use-fn
(mf/deps ids proportion-lock)
@@ -308,26 +335,43 @@
(mf/deps ids)
(fn [value attr]
(if (or (string? value) (int? value))
(do (st/emit! (udw/trigger-bounding-box-cloaking ids))
(binding [cts/*wasm-sync* true]
(run! #(do-position-change %1 value attr) shapes)))
(do
(let [value2 (:resolved-value value)]
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(binding [cts/*wasm-sync* true]
(run! #(do-position-change %1 value attr) shapes)))
(do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token value
(dwta/toggle-token {:token (first value)
:attrs #{attr}
:shapes shapes}))
(binding [cts/*wasm-sync* true]
(run! #(do-position-change %1 value2 attr) shapes)))))))
(run! #(do-position-change %1 resolved-value attr) shapes)))))))
;; ROTATION
do-rotation-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (udw/increase-rotation ids value))))
on-rotation-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(binding [cts/*wasm-sync* true]
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(udw/increase-rotation ids value)))))
(if (or (string? value) (int? value))
(do
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(binding [cts/*wasm-sync* true]
(run! #(do-rotation-change value) shapes)))
(do
(let [resolved-value (:resolved-value (first value))]
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value)
:attrs #{:rotation}
:shapes shapes}))
(binding [cts/*wasm-sync* true]
(run! #(do-rotation-change resolved-value) shapes)))))))
on-width-change
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
@@ -341,15 +385,16 @@
on-pos-y-change
(mf/use-fn (mf/deps on-position-change) #(on-position-change % :y))
;; DETACH
on-detach-token
(mf/use-fn
(mf/deps ids)
(fn [token attr]
;; Review this, detach is having problems
(let [shape-ids (map :id shapes)]
(st/emit! (dwta/unapply-token {:token token
:attributes #{attr}
:shape-ids shape-ids})))))
(st/emit! (dwta/unapply-token {:token (first token)
:attributes #{attr}
:shape-ids ids}))))
;; CLIP CONTENT AND SHOW IN VIEWER
on-change-clip-content
(mf/use-fn
@@ -432,28 +477,55 @@
(when (options :size)
[:div {:class (stl/css :size)}
[:div {:class (stl/css-case :width true
:disabled disabled-width-sizing?)
:title (tr "workspace.options.width")}
[:span {:class (stl/css :icon-text)} "W"]
[:> numeric-input* {:min 0.01
:no-validate true
:placeholder (if (= :multiple (:width values)) (tr "settings.multiple") "--")
:on-change on-width-change
:disabled disabled-width-sizing?
:class (stl/css :numeric-input)
:value (:width values)}]]
[:div {:class (stl/css-case :height true
:disabled disabled-height-sizing?)
:title (tr "workspace.options.height")}
[:span {:class (stl/css :icon-text)} "H"]
[:> numeric-input* {:min 0.01
:no-validate true
:placeholder (if (= :multiple (:height values)) (tr "settings.multiple") "--")
:on-change on-height-change
:disabled disabled-height-sizing?
:class (stl/css :numeric-input)
:value (:height values)}]]
(if token-numeric-inputs
[:*
[:> numeric-input-wrapper*
{:disabled disabled-width-sizing?
:on-change on-width-change
:on-detach on-detach-token
:icon i/character-w
:min 0.01
:name :width
:property (tr "workspace.options.width")
:applied-tokens applied-tokens
:values values}]
[:> numeric-input-wrapper*
{:disabled disabled-height-sizing?
:on-change on-height-change
:on-detach on-detach-token
:min 0.01
:icon i/character-h
:name :height
:align :right
:property (tr "workspace.options.height")
:applied-tokens applied-tokens
:values values}]]
[:*
[:div {:class (stl/css-case :width true
:disabled disabled-width-sizing?)
:title (tr "workspace.options.width")}
[:span {:class (stl/css :icon-text)} "W"]
[:> deprecated-input/numeric-input*
{:min 0.01
:no-validate true
:placeholder (if (= :multiple (:width values)) (tr "settings.multiple") "--")
:on-change on-width-change
:disabled disabled-width-sizing?
:class (stl/css :numeric-input)
:value (:width values)}]]
[:div {:class (stl/css-case :height true
:disabled disabled-height-sizing?)
:title (tr "workspace.options.height")}
[:span {:class (stl/css :icon-text)} "H"]
[:> deprecated-input/numeric-input* {:min 0.01
:no-validate true
:placeholder (if (= :multiple (:height values)) (tr "settings.multiple") "--")
:on-change on-height-change
:disabled disabled-height-sizing?
:class (stl/css :numeric-input)
:value (:height values)}]]])
[:> icon-button* {:variant "ghost"
:icon (if proportion-lock "lock" "unlock")
@@ -464,45 +536,84 @@
(when (options :position)
[:div {:class (stl/css :position)}
[:> numeric-input-wrapper*
{:disabled disabled-position?
:on-change on-position-change
:on-detach on-detach-token
:icon "character-x"
:name :x
:property (tr "workspace.options.x")
:applied-tokens applied-tokens
:values values}]
(if token-numeric-inputs
[:*
[:> numeric-input-wrapper*
{:disabled disabled-position?
:on-change on-pos-x-change
:on-detach on-detach-token
:icon i/character-x
:name :x
:property (tr "workspace.options.x")
:applied-tokens applied-tokens
:values values}]
[:> numeric-input-wrapper*
{:disabled disabled-position?
:on-change on-pos-y-change
:on-detach on-detach-token
:icon i/character-y
:name :y
:align :right
:property (tr "workspace.options.y")
:applied-tokens applied-tokens
:values values}]]
[:> numeric-input-wrapper*
{:disabled disabled-position?
:on-change on-position-change
:on-detach on-detach-token
:icon "character-y"
:name :y
:property (tr "workspace.options.y")
:applied-tokens applied-tokens
:values values}]])
[:*
[:div {:class (stl/css-case :x-position true
:disabled disabled-position?)
:title (tr "workspace.options.x")}
[:span {:class (stl/css :icon-text)} "X"]
[:> deprecated-input/numeric-input* {:no-validate true
:placeholder (if (= :multiple (:x values)) (tr "settings.multiple") "--")
:on-change on-pos-x-change
:disabled disabled-position?
:class (stl/css :numeric-input)
:value (:x values)}]]
[:div {:class (stl/css-case :y-position true
:disabled disabled-position?)
:title (tr "workspace.options.y")}
[:span {:class (stl/css :icon-text)} "Y"]
[:> deprecated-input/numeric-input* {:no-validate true
:placeholder (if (= :multiple (:y values)) (tr "settings.multiple") "--")
:disabled disabled-position?
:on-change on-pos-y-change
:class (stl/css :numeric-input)
:value (:y values)}]]])])
(when (or (options :rotation) (options :radius))
[:div {:class (stl/css :rotation-radius)}
(when (options :rotation)
[:div {:class (stl/css :rotation)
:title (tr "workspace.options.rotation")}
[:span {:class (stl/css :icon)} deprecated-icon/rotation]
[:> numeric-input*
{:no-validate true
:min -359
:max 359
:data-wrap true
:placeholder (if (= :multiple (:rotation values)) (tr "settings.multiple") "--")
:on-change on-rotation-change
:class (stl/css :numeric-input)
:value (:rotation values)}]])
(if token-numeric-inputs
[:> numeric-input-wrapper*
{:on-change on-rotation-change
:on-detach on-detach-token
:icon i/rotation
:min -359
:max 359
:name :rotation
:property (tr "workspace.options.rotation")
:applied-tokens applied-tokens
:values values}]
[:div {:class (stl/css :rotation)
:title (tr "workspace.options.rotation")}
[:span {:class (stl/css :icon)} deprecated-icon/rotation]
[:> deprecated-input/numeric-input*
{:no-validate true
:min -359
:max 359
:data-wrap true
:placeholder (if (= :multiple (:rotation values)) (tr "settings.multiple") "--")
:on-change on-rotation-change
:class (stl/css :numeric-input)
:value (:rotation values)}]]))
(when (options :radius)
[:> border-radius-menu* {:class (stl/css :border-radius)
:ids ids
:values values
:applied-tokens applied-tokens
:shapes shapes
:shape shape}])])
(when (or (options :clip-content) (options :show-in-viewer))
[:div {:class (stl/css :clip-show)}