♻️ Refactor small numeric inputs (#8660)

* ♻️ Refactor individual border radius inputs

* ♻️ Refactor layer opacity input

* ♻️ Refactor stroke width inputs and add icon only selects

* ♻️ Fix comments on PR
This commit is contained in:
Eva Marco
2026-03-23 11:00:29 +01:00
committed by GitHub
parent dc56da9662
commit 72fd637ec2
31 changed files with 484 additions and 208 deletions

View File

@@ -72,7 +72,7 @@ test.describe("Tokens: Apply token", () => {
// Check if border radius sections is visible on right sidebar
const borderRadiusSection = page.getByRole("region", {
name: "border-radius-section",
name: "Border radius section",
});
await expect(borderRadiusSection).toBeVisible();
@@ -135,7 +135,7 @@ test.describe("Tokens: Apply token", () => {
// Check if opacity sections is visible on right sidebar
const layerMenuSection = page.getByRole("region", {
name: "layer-menu-section",
name: "Layer menu section",
});
await expect(layerMenuSection).toBeVisible();
@@ -688,7 +688,7 @@ test.describe("Tokens: Apply token", () => {
// Check if border radius sections is visible on right sidebar
const borderRadiusSection = page.getByRole("region", {
name: "border-radius-section",
name: "Border radius section",
});
await expect(borderRadiusSection).toBeVisible();
@@ -897,7 +897,7 @@ test.describe("Tokens: Detach token", () => {
// Check if border radius sections is visible on right sidebar
const borderRadiusSection = page.getByRole("region", {
name: "border-radius-section",
name: "Border radius section",
});
await expect(borderRadiusSection).toBeVisible();

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path fill="#8f9da3" fill-opacity="0.2" stroke="none" fill-rule="evenodd" d="M0 0h16v16H0z M6 6h4v4H6z"/>
<rect x="3.5" y="3.5" width="9" height="9"/>
<path d="M3 3h.01M13 3h.01M3 13h.01M13 13h.01" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 371 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 8h4M10 8h4"/>
</svg>

After

Width:  |  Height:  |  Size: 176 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 8h.01M8 8h.01M13 8h.01"/>
</svg>

After

Width:  |  Height:  |  Size: 205 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path fill="#8f9da3" fill-opacity="0.2" stroke="none" fill-rule="evenodd" d="M0 0h16v16H0z M6 6h4v4H6z"/>
<rect x="5.5" y="5.5" width="5" height="5"/>
<path d="M5 5h.01M11 5h.01M5 11h.01M11 11h.01" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 371 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 8h5"/>
<path d="M13 8h.01" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 210 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path fill="#8f9da3" fill-opacity="0.2" stroke="none" fill-rule="evenodd" d="M0 0h16v16H0z M6 6h4v4H6z"/>
<rect x="1.5" y="1.5" width="13" height="13"/>
<path d="M1 1h.01M15 1h.01M1 15h.01M15 15h.01" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 8h10"/>
</svg>

After

Width:  |  Height:  |  Size: 171 B

View File

@@ -822,9 +822,50 @@
:shape-ids shape-ids
:on-update-shape on-update-shape}))))))))
(defn apply-token-on-selected
(defn apply-token-from-input
[{:keys [token attrs shape-ids expand-with-children]}]
(ptk/reify ::apply-token-from-input
ptk/WatchEvent
(watch [_ state _]
(let [objects (dsh/lookup-page-objects state)
shapes (into [] (keep (d/getf objects)) shape-ids)
shapes
(if expand-with-children
(into []
(mapcat (fn [shape]
(if (= (:type shape) :group)
(keep objects (:shapes shape))
[shape])))
shapes)
shapes)
{:keys [attributes _ on-update-shape]}
(get token-properties (:type token))
on-update-shape
(if (seq attrs)
(or (get attr->shape-update (first attrs)) on-update-shape)
on-update-shape)]
(rx/of
(cond
(and (= (:type token) :spacing)
(nil? attrs))
(apply-spacing-token-separated {:token token
:attr attrs
:shapes shapes})
:else
(apply-token {:attributes (if (empty? attrs) attributes attrs)
:token token
:shape-ids shape-ids
:on-update-shape on-update-shape})))))))
(defn apply-token-on-color-selected
[color-operations token]
(ptk/reify ::apply-token-on-selected
(ptk/reify ::apply-token-on-color-selected
ptk/WatchEvent
(watch [_ _ _]
(let [undo-id (js/Symbol)]

View File

@@ -140,6 +140,8 @@
[:on-focus {:optional true} fn?]
[:on-detach {:optional true} fn?]
[:property {:optional true} :string]
[:tooltip-placement {:optional true}
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]
[:align {:optional true} [:maybe [:enum :left :right]]]])
(mf/defc numeric-input*
@@ -151,7 +153,7 @@
tokens applied-token empty-to-end
on-change on-blur on-focus on-detach
property align ref name
text-icon]
tooltip-placement text-icon]
:rest props}]
(let [;; NOTE: we use mfu/bean here for transparently handle
@@ -574,9 +576,9 @@
:icon i/tokens
:tooltip-class (stl/css :button-tooltip)
:class (stl/css :invisible-button)
:tooltip-placement "top-left"
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
:ref open-dropdown-ref
:tooltip-placement tooltip-placement
:on-click open-dropdown}])))
:max-length max-length})
@@ -603,6 +605,7 @@
:class inner-class
:property property
:is-open is-open
:tooltip-placement tooltip-placement
:slot-start (when (or icon text-icon)
(mf/html
(cond

View File

@@ -9,8 +9,10 @@
[app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown* schema:option]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
@@ -50,15 +52,17 @@
[:map
[:options [:vector {:min 1} schema:option]]
[:class {:optional true} :string]
[:wrapper-class {:optional true} :string]
[:disabled {:optional true} :boolean]
[:default-selected {:optional true} :string]
[:empty-to-end {:optional true} [:maybe :boolean]]
[:on-change {:optional true} fn?]
[:variant {:optional true} [:maybe [:enum "default" "ghost"]]]])
[:dropdown-alignment {:optional true} [:maybe [:enum :left :right]]]
[:variant {:optional true} [:maybe [:enum "default" "ghost" "icon-only"]]]])
(mf/defc select*
{::mf/schema schema:select}
[{:keys [options class disabled default-selected empty-to-end on-change variant] :rest props}]
[{:keys [options class disabled default-selected empty-to-end on-change variant wrapper-class dropdown-alignment] :rest props}]
(let [;; NOTE: we use mfu/bean here for transparently handle
;; options provide as clojure data structures or javascript
;; plain objects and lists.
@@ -192,26 +196,40 @@
(some? icon)
dimmed?
(:dimmed selected-option)]
(:dimmed selected-option)
icon-ref (mf/use-ref nil)
icon-id (mf/use-id)]
(mf/with-effect [options]
(mf/set-ref-val! options-ref options))
[:div {:class (stl/css :select-wrapper)
[:div {:class [wrapper-class (stl/css :select-wrapper)]
:on-click on-click
:ref select-ref
:on-blur on-blur}
[:> :button props
[:span {:class (stl/css-case :select-header true
:header-icon has-icon?)}
:header-icon has-icon?
:header-icon-only (= variant "icon-only"))}
(when ^boolean has-icon?
[:> icon* {:icon-id icon
:size "s"
:aria-hidden true}])
[:span {:class (stl/css-case :header-label true
:header-label-dimmed (or empty-selected-id? dimmed?))}
(if ^boolean empty-selected-id? "--" label)]]
(if (= variant "icon-only")
[:> tooltip* {:content label
:trigger-ref icon-ref
:id (dm/str icon-id "-name")
:class (stl/css :option-text)}
[:> icon* {:icon-id icon
:ref icon-ref
:aria-labelledby (dm/str icon-id "-name")}]]
[:> icon* {:icon-id icon
:size "s"
:aria-hidden true}]))
(when-not ^boolean (= variant "icon-only")
[:span {:class (stl/css-case :header-label true
:header-label-dimmed (or empty-selected-id? dimmed?))}
(if ^boolean empty-selected-id? "--" label)])]
[:> icon* {:icon-id i/arrow-down
:class (stl/css :arrow)
@@ -224,5 +242,6 @@
:options options
:selected selected-id
:focused focused-id
:align dropdown-alignment
:empty-to-end empty-to-end
:ref set-option-ref}])]))

View File

@@ -109,3 +109,8 @@
grid-template-columns: auto 1fr;
color: var(--select-icon-color);
}
.header-icon-only {
grid-template-columns: 1fr;
color: var(--select-icon-color);
}

View File

@@ -9,7 +9,7 @@ import Components from "@target/components";
const { Select } = Components;
const variants = ["default", "ghost"];
const variants = ["default", "ghost", "icon-only"];
const options = [
{ id: "option-code", label: "Code" },
@@ -75,3 +75,10 @@ export const EmptyToEnd = {
emptyToEnd: true,
},
};
export const OnlyWithIcons = {
args: {
options: optionsWithIcons,
variant: variants[2],
},
};

View File

@@ -28,6 +28,7 @@
[:resolved-value {:optional true}
[:or :int :string :float]]
[:name {:optional true} :string]
[:value {:optional true} :keyword]
[:icon {:optional true} schema:icon-list]
[:label {:optional true} :string]
[:aria-label {:optional true} :string]])

View File

@@ -31,12 +31,14 @@
[:on-token-key-down fn?]
[:on-blur {:optional true} fn?]
[:on-focus {:optional true} fn?]
[:tooltip-placement {:optional true}
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]
[:detach-token fn?]])
(mf/defc token-field*
{::mf/schema schema:token-field}
[{:keys [id label value slot-start disabled class
on-click on-token-key-down on-blur detach-token
on-click on-token-key-down on-blur detach-token tooltip-placement
token-wrapper-ref token-detach-btn-ref on-focus property is-open]}]
(let [set-active? (some? id)
content (if set-active?
@@ -92,8 +94,8 @@
:class (stl/css-case :invisible-button true
:invisible-btn-dropdown-open is-open)
:tooltip-class (stl/css :button-tooltip)
:tooltip-placement tooltip-placement
:icon i/broken-link
:ref token-detach-btn-ref
:tooltip-placement "top-left"
:aria-label (tr "ds.inputs.token-field.detach-token")
:on-click detach-token}])]]))

View File

@@ -245,12 +245,19 @@
(def ^:icon-id status-update "status-update")
(def ^:icon-id status-wrong "status-wrong")
(def ^:icon-id stroke-arrow "stroke-arrow")
(def ^:icon-id stroke-center "stroke-center")
(def ^:icon-id stroke-circle "stroke-circle")
(def ^:icon-id stroke-dashed "stroke-dashed")
(def ^:icon-id stroke-diamond "stroke-diamond")
(def ^:icon-id stroke-dotted "stroke-dotted")
(def ^:icon-id stroke-inside "stroke-inside")
(def ^:icon-id stroke-mixed "stroke-mixed")
(def ^:icon-id stroke-outside "stroke-outside")
(def ^:icon-id stroke-rectangle "stroke-rectangle")
(def ^:icon-id stroke-rounded "stroke-rounded")
(def ^:icon-id stroke-size "stroke-size")
(def ^:icon-id stroke-squared "stroke-squared")
(def ^:icon-id stroke-solid "stroke-solid")
(def ^:icon-id stroke-triangle "stroke-triangle")
(def ^:icon-id svg "svg")
(def ^:icon-id swatches "swatches")

View File

@@ -43,11 +43,15 @@ $column-number: 8; // total number of columns
// -> 8 columns (32px each) + 7 gaps (4px each) = 284px
// Derived widths
$options-width: calc(#{$column-width} * #{$column-number} + #{$column-gap} * calc(#{$column-number} - 1));
$seven-column-width: calc(
#{$column-width} * calc(#{$column-number} - 1) + #{$column-gap} * calc(#{$column-number} - 2)
);
@function grid-width($cols) {
@return calc(#{$column-width} * #{$cols} + #{$column-gap} * #{$cols - 1});
}
$options-width: grid-width($column-number);
$two-column-width: grid-width(2);
$three-column-width: grid-width(3);
$four-column-width: grid-width(4);
$seven-column-width: grid-width(7);
// ------------------------------------------------------------
// Grid mixin — applies the standard structure to any container
// ------------------------------------------------------------
@@ -73,16 +77,28 @@ $seven-column-width: calc(
// |___|-|___|-|___|-|___|-|___|-|___|-|___|-|___|
// -> 8 columns (32px each) + 7 gaps (4px each) = 284px
//
// But one block (grid-exception-input) doesnt fit perfectly:
// But two blocks dont fit perfectly:
// First (grid-exception-input-width)
// |__________________|-|__________________|-|___|
//
// We calculate the width of that grid-exception-input as:
// We calculate the width of that grid-exception-input-width as:
//
// - 3.5 columns of base grid width
// - + 3 inter-column gaps
// - half a gap (because its visually shared with the next block)
$grid-exception-input-width: calc(#{$sz-32} * 3.5 + 3 * var(--sp-xs) - (var(--sp-xs) / 2));
//
// |___|-|___|-|___|-|___|-|___|-|___|-|___|-|___|
//
// Second (grid-exception-input-width-small)
// |__________________|-|____________|-|___|-|___|
//
// We calculate the width of that grid-exception-input-width-small as:
//
// - 2.5 columns of base grid width
// - + 2 inter-column gaps
// - half a gap (because its visually shared with the next block)
$grid-exception-input-width-small: calc(#{$sz-32} * 2.5 + 2 * var(--sp-xs) - (var(--sp-xs) / 2));
// ============================================================
// CSS VARIABLES (exposed for runtime use)
@@ -95,7 +111,11 @@ $grid-exception-input-width: calc(#{$sz-32} * 3.5 + 3 * var(--sp-xs) - (var(--sp
--left-sidebar-width-max: #{$left-sidebar-width-max};
--right-sidebar-width: #{$right-sidebar-width};
--right-sidebar-width-max: #{$right-sidebar-width-max};
--7-columns-dropdown-width: #{$seven-column-width};
--2-columns-width: #{$two-column-width};
--3-columns-width: #{$three-column-width};
--4-columns-width: #{$four-column-width};
--7-columns-width: #{$seven-column-width};
--options-width: #{$options-width};
--grid-exception-input-width: #{$grid-exception-input-width};
--grid-exception-input-width-small: #{$grid-exception-input-width-small};
}

View File

@@ -164,64 +164,50 @@
(mf/with-effect [ids]
(reset! radius-expanded* false))
[:section {:class (dm/str class " " (stl/css :radius))
:aria-label "border-radius-section"}
(if (not radius-expanded)
(if token-numeric-inputs
[:> numeric-input-wrapper*
{:on-change on-all-radius-change
:on-detach on-detach-all
:icon i/corner-radius
:min 0
:attr :border-radius
:nillable true
:property (tr "workspace.options.radius")
:applied-token (cond
(not (seq applied-tokens))
nil
(if token-numeric-inputs
[:section {:class (dm/str class " " (stl/css :radius-token))
:aria-label (tr "workspace.options.radius.radius-section")}
[:div {:class (stl/css :radius-first-row)}
[:> numeric-input-wrapper*
{:on-change on-all-radius-change
:on-detach on-detach-all
:icon i/corner-radius
:min 0
:attr :border-radius
:nillable true
:property (tr "workspace.options.radius")
:applied-token (cond
(not (seq applied-tokens))
nil
(or (not all-values-equal?) (not all-token-equal?))
:multiple
(or (not all-values-equal?) (not all-token-equal?))
:multiple
:else
(get applied-tokens :r1))
:align :right
:placeholder (cond
(or (not all-values-equal?)
(not all-token-equal?))
(tr "settings.multiple")
:else
"--")
:value (if all-values-equal?
(if (nil? (:r1 values))
0
(:r1 values))
nil)}]
[:div {:class (stl/css :radius-1)
:title (tr "workspace.options.radius")}
[:> icon* {:icon-id i/corner-radius
:size "s"
:class (stl/css :icon)}]
[:> deprecated-input/numeric-input*
{:placeholder (cond
(not all-values-equal?)
(tr "settings.multiple")
(= :multiple (:r1 values))
(tr "settings.multiple")
:else
"--")
:min 0
:nillable true
:on-change on-all-radius-change
:value (if all-values-equal?
(if (nil? (:r1 values))
0
(:r1 values))
nil)}]])
(get applied-tokens :r1))
:align :right
:placeholder (cond
(or (not all-values-equal?)
(not all-token-equal?))
(tr "settings.multiple")
:else
"--")
:value (if all-values-equal?
(if (nil? (:r1 values))
0
(:r1 values))
nil)}]
[:> icon-button* {:class (stl/css-case :selected radius-expanded)
:variant "ghost"
:tooltip-placement "top-left"
:on-click toggle-radius-mode
:aria-label (if radius-expanded
(tr "workspace.options.radius.hide-all-corners")
(tr "workspace.options.radius.show-single-corners"))
:icon i/corner-radius}]]
(if token-numeric-inputs
[:div {:class (stl/css :radius-4)}
(when radius-expanded
[:div {:class (stl/css :radius-4-token)}
[:> numeric-input-wrapper*
{:on-change on-radius-r1-change
:on-detach on-detach-r1
@@ -249,6 +235,7 @@
:property (tr "workspace.options.radius-top-right")
:applied-token (get applied-tokens :r2)
:align :right
:tooltip-placement "top-left"
:inner-class (stl/css :no-icon-input)
:placeholder (cond
(or (= :multiple (get applied-tokens :r2))
@@ -293,9 +280,33 @@
"--")
:align :right
:class (stl/css :radius-wrapper)
:tooltip-placement "top-left"
:inner-class (stl/css :no-icon-input)
:value (:r3 values)}]]
:value (:r3 values)}]])]
[:section {:class (dm/str class " " (stl/css :radius))
:aria-label (tr "workspace.options.radius.radius-section")}
(if (not radius-expanded)
[:div {:class (stl/css :radius-1)
:title (tr "workspace.options.radius")}
[:> icon* {:icon-id i/corner-radius
:size "s"
:class (stl/css :icon)}]
[:> deprecated-input/numeric-input*
{:placeholder (cond
(not all-values-equal?)
(tr "settings.multiple")
(= :multiple (:r1 values))
(tr "settings.multiple")
:else
"--")
:min 0
:nillable true
:on-change on-all-radius-change
:value (if all-values-equal?
(if (nil? (:r1 values))
0
(:r1 values))
nil)}]]
[:div {:class (stl/css :radius-4)}
[:div {:class (stl/css :small-input)}
[:> deprecated-input/numeric-input*
@@ -327,12 +338,11 @@
:title (tr "workspace.options.radius-bottom-right")
:min 0
:on-change on-radius-r3-change
:value (:r3 values)}]]]))
[:> icon-button* {:class (stl/css-case :selected radius-expanded)
:variant "ghost"
:on-click toggle-radius-mode
:aria-label (if radius-expanded
(tr "workspace.options.radius.hide-all-corners")
(tr "workspace.options.radius.show-single-corners"))
:icon i/corner-radius}]]))
:value (:r3 values)}]]])
[:> icon-button* {:class (stl/css-case :selected radius-expanded)
:variant "ghost"
:on-click toggle-radius-mode
:aria-label (if radius-expanded
(tr "workspace.options.radius.hide-all-corners")
(tr "workspace.options.radius.show-single-corners"))
:icon i/corner-radius}]])))

View File

@@ -14,7 +14,8 @@
gap: var(--sp-xs);
}
.radius-1 {
.radius-1,
.small-input {
@extend .input-element;
@include t.use-typography("body-small");
}
@@ -25,23 +26,12 @@
gap: var(--sp-xs);
}
.small-input {
@extend .input-element;
@include t.use-typography("body-small");
}
.selected {
border-color: var(--button-icon-border-color-selected);
background-color: var(--button-icon-background-color-selected);
color: var(--color-accent-primary);
}
.selected {
border-color: var(--button-icon-border-color-selected);
background-color: var(--button-icon-background-color-selected);
color: var(--button-icon-foreground-color-selected);
}
.icon {
margin-inline: var(--sp-xs);
}
@@ -53,3 +43,20 @@
.dropdown-offset {
--dropdown-offset: #{px2rem(-65)};
}
.radius-token {
display: grid;
gap: var(--sp-xs);
}
.radius-first-row {
display: grid;
grid-template-columns: 1fr auto;
gap: var(--sp-xs);
}
.radius-4-token {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--sp-xs);
}

View File

@@ -190,7 +190,7 @@
[color-operations _] (retrieve-color-operations groups old-color prev-colors)]
(mf/set-ref-val! prev-colors-ref
(conj prev-colors color))
(st/emit! (dwta/apply-token-on-selected color-operations token)))))]
(st/emit! (dwta/apply-token-on-color-selected color-operations token)))))]
[:div {:class (stl/css :element-set)}
[:div {:class (stl/css :element-title)}

View File

@@ -174,10 +174,10 @@
(mf/deps ids)
(fn [_ token]
(st/emit!
(dwta/toggle-token {:token token
:attrs #{:fill}
:shape-ids ids
:expand-with-children true}))))
(dwta/apply-token-from-input {:token token
:attrs #{:fill}
:shape-ids ids
:expand-with-children true}))))
on-detach-token
(mf/use-fn

View File

@@ -5,5 +5,5 @@
// Copyright (c) KALEIDOS INC
.numeric-input-wrapper {
--dropdown-width: var(--7-columns-dropdown-width);
--dropdown-width: var(--7-columns-width);
}

View File

@@ -138,14 +138,13 @@
on-opacity-change
(mf/use-fn
(mf/deps on-change handle-opacity-change)
(mf/deps handle-opacity-change)
(fn [value]
(if (or (string? value) (number? value))
(handle-opacity-change value)
(do
(st/emit! (dwta/toggle-token {:token (first value)
:attrs #{:opacity}
:shape-ids ids}))))))
(st/emit! (dwta/apply-token-from-input {:token (first value)
:attrs #{:opacity}
:shape-ids ids})))))
handle-set-hidden
(mf/use-fn
@@ -205,20 +204,25 @@
preview-complete?))
(swap! state* assoc :selected-blend-mode current-blend-mode)))
[:section {:class (stl/css-case :element-set-content true
:hidden hidden?)
:aria-label "layer-menu-section"}
[:div {:class (stl/css :select)}
[:& select
{:default-value selected-blend-mode
:options options
:on-change handle-change-blend-mode
:is-open? option-highlighted?
:class (stl/css-case :hidden-select hidden?)
:on-pointer-enter-option handle-blend-mode-enter
:on-pointer-leave-option handle-blend-mode-leave}]]
;; NOTE:
;; This code is temporarily duplicated because the UI is changing with a new feature.
;; The new implementation is currently behind a feature/config flag and not yet released.
;; Once the feature is released, the duplicated ClojureScript and SCSS code should be removed.
;; https://tree.taiga.io/project/penpot/task/13704
(if token-numeric-inputs
;; TODO: When duplicated code is remove rename this class removing the "token" reference from it
[:section {:class (stl/css :element-set-content-token)
:aria-label (tr "workspace.options.layer-options.layer-section")}
[:& select
{:default-value selected-blend-mode
:options options
:on-change handle-change-blend-mode
:is-open? option-highlighted?
:class (stl/css-case :hidden-select hidden?)
:on-pointer-enter-option handle-blend-mode-enter
:on-pointer-leave-option handle-blend-mode-leave}]
(if token-numeric-inputs
[:> numeric-input-wrapper*
{:on-change on-opacity-change
:on-detach on-detach-token
@@ -233,10 +237,54 @@
(tr "settings.multiple")
"--")
:align :right
:disabled hidden?
:class (stl/css :numeric-input-wrapper)
:value (* 100
(or (get values :opacity) 1))}]
(cond
(or (= :multiple hidden?) (not hidden?))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.layer-options.toggle-layer")
:on-click handle-set-hidden
:tooltip-placement "top-left"
:icon i/shown}]
:else
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.layer-options.toggle-layer")
:on-click handle-set-visible
:tooltip-placement "top-left"
:icon i/hide}])
(cond
(or (= :multiple blocked?) (not blocked?))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.lock")
:on-click handle-set-blocked
:tooltip-placement "top-left"
:icon i/unlock}]
:else
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.unlock")
:on-click handle-set-unblocked
:tooltip-placement "top-left"
:icon i/lock}])]
[:section {:class (stl/css-case :element-set-content true
:hidden hidden?)
:aria-label (tr "workspace.options.layer-options.layer-section")}
[:div {:class (stl/css :select)}
[:& select
{:default-value selected-blend-mode
:options options
:on-change handle-change-blend-mode
:is-open? option-highlighted?
:class (stl/css-case :hidden-select hidden?)
:on-pointer-enter-option handle-blend-mode-enter
:on-pointer-leave-option handle-blend-mode-leave}]]
[:div {:class (stl/css :input)
:title (tr "workspace.options.opacity")}
[:span {:class (stl/css :icon)} "%"]
@@ -246,31 +294,31 @@
:on-change handle-opacity-change
:min 0
:max 100
:className (stl/css :numeric-input)}]])
:className (stl/css :numeric-input)}]]
[:div {:class (stl/css :actions)}
(cond
(or (= :multiple hidden?) (not hidden?))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.layer-options.toggle-layer")
:on-click handle-set-hidden
:icon i/shown}]
[:div {:class (stl/css :actions)}
(cond
(or (= :multiple hidden?) (not hidden?))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.layer-options.toggle-layer")
:on-click handle-set-hidden
:icon i/shown}]
:else
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.layer-options.toggle-layer")
:on-click handle-set-visible
:icon i/hide}])
:else
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.options.layer-options.toggle-layer")
:on-click handle-set-visible
:icon i/hide}])
(cond
(or (= :multiple blocked?) (not blocked?))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.lock")
:on-click handle-set-blocked
:icon i/unlock}]
(cond
(or (= :multiple blocked?) (not blocked?))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.lock")
:on-click handle-set-blocked
:icon i/unlock}]
:else
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.unlock")
:on-click handle-set-unblocked
:icon i/lock}])]]))
:else
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.shape.menu.unlock")
:on-click handle-set-unblocked
:icon i/lock}])]])))

View File

@@ -7,18 +7,23 @@
@use "refactor/common-refactor.scss" as deprecated;
@use "../../../sidebar/common/sidebar.scss" as sidebar;
@use "ds/_utils.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
@use "ds/typography.scss" as t;
// This code should be remove when numeric-input-tokens are activated
// https://tree.taiga.io/project/penpot/task/13704
.element-set-content {
@include sidebar.option-grid-structure;
height: deprecated.$s-32;
margin-bottom: deprecated.$s-8;
block-size: $sz-32;
margin-block-end: var(--sp-s);
.select {
grid-column: span 4;
padding: 0;
}
.input {
@extend .input-element;
@include deprecated.bodySmallTypography;
@include t.use-typography("body-small");
grid-column: span 2;
}
.actions {
@@ -29,12 +34,22 @@
&.hidden {
.hidden-select {
@include deprecated.hiddenElement;
border: deprecated.$s-1 solid var(--input-border-color-disabled);
cursor: default;
pointer-events: none;
box-sizing: border-box;
color: var(--input-foreground-color-disabled);
stroke: var(--input-foreground-color-disabled);
background-color: transparent;
border: $b-1 solid var(--input-border-color-disabled);
}
.input {
@include deprecated.hiddenElement;
border: deprecated.$s-1 solid var(--input-border-color-disabled);
cursor: default;
pointer-events: none;
box-sizing: border-box;
color: var(--input-foreground-color-disabled);
stroke: var(--input-foreground-color-disabled);
background-color: transparent;
border: $b-1 solid var(--input-border-color-disabled);
.icon {
stroke: var(--input-foreground-color-disabled);
}
@@ -45,7 +60,28 @@
}
}
// This code should remain when numeric-input-tokens are activated
// https://tree.taiga.io/project/penpot/task/13704
// This rule should be rename when numeric-input-tokens are
// activated removing the token reference on the class
.element-set-content-token {
@include sidebar.option-grid-structure;
block-size: $sz-32;
margin-block-end: var(--sp-s);
grid-template-columns: var(--grid-exception-input-width) var(--grid-exception-input-width-small) auto auto;
}
.hidden-select {
cursor: default;
pointer-events: none;
box-sizing: border-box;
color: var(--input-foreground-color-disabled);
stroke: var(--input-foreground-color-disabled);
background-color: transparent;
border: $b-1 solid var(--input-border-color-disabled);
}
.numeric-input-wrapper {
grid-column: span 2;
--dropdown-offset: #{px2rem(-35)};
}

View File

@@ -463,9 +463,9 @@
(if (or (string? value) (number? value))
(on-change :multiple attr value event)
(do
(st/emit! (dwta/toggle-token {:token (first value)
:attrs #{attr}
:shape-ids ids}))))))
(st/emit! (dwta/apply-token-from-input {:token (first value)
:attrs #{attr}
:shape-ids ids}))))))
on-focus
(mf/use-fn

View File

@@ -283,9 +283,9 @@
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(udw/update-dimensions ids attr value))
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value)
:attrs #{attr}
:shape-ids ids})))))
(dwta/apply-token-from-input {:token (first value)
:attrs #{attr}
:shape-ids ids})))))
on-proportion-lock-change
(mf/use-fn
@@ -304,9 +304,9 @@
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(st/emit! (udw/update-positions ids {attr value})))
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value)
:attrs #{attr}
:shape-ids ids})))))
(dwta/apply-token-from-input {:token (first value)
:attrs #{attr}
:shape-ids ids})))))
on-rotation-change
(mf/use-fn
@@ -317,9 +317,9 @@
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(st/emit! (udw/increase-rotation ids value)))
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(dwta/toggle-token {:token (first value)
:attrs #{:rotation}
:shape-ids ids})))))
(dwta/apply-token-from-input {:token (first value)
:attrs #{:rotation}
:shape-ids ids})))))
on-width-change
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))

View File

@@ -191,5 +191,5 @@
// TODO: Add a proper variable to this sizing
.numeric-input-measures {
--dropdown-width: var(--7-columns-dropdown-width);
--dropdown-width: var(--7-columns-width);
}

View File

@@ -16,6 +16,7 @@
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.select :refer [select*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.hooks :as h]
[app.main.ui.workspace.sidebar.options.common :as soc]
@@ -108,9 +109,9 @@
(d/concat-vec
(when (= :multiple stroke-alignment)
[{:value :multiple :label "--"}])
[{:value :center :label (tr "workspace.options.stroke.center")}
{:value :inner :label (tr "workspace.options.stroke.inner")}
{:value :outer :label (tr "workspace.options.stroke.outer")}]))
[{:value :center :label (tr "workspace.options.stroke.center") :id "center" :icon "stroke-center"}
{:value :inner :label (tr "workspace.options.stroke.inner") :id "inner" :icon "stroke-inside"}
{:value :outer :label (tr "workspace.options.stroke.outer") :id "outer" :icon "stroke-outside"}]))
on-alignment-change
(mf/use-fn
@@ -122,10 +123,10 @@
(mf/deps ids)
(fn [_ token]
(st/emit!
(dwta/toggle-token {:token token
:attrs #{:stroke-color}
:shape-ids ids
:expand-with-children true}))))
(dwta/apply-token-from-input {:token token
:attrs #{:stroke-color}
:shape-ids ids
:expand-with-children true}))))
stroke-style (or (:stroke-style stroke) :solid)
@@ -134,10 +135,10 @@
(d/concat-vec
(when (= :multiple stroke-style)
[{:value :multiple :label "--"}])
[{:value :solid :label (tr "workspace.options.stroke.solid")}
{:value :dotted :label (tr "workspace.options.stroke.dotted")}
{:value :dashed :label (tr "workspace.options.stroke.dashed")}
{:value :mixed :label (tr "workspace.options.stroke.mixed")}]))
[{:value :solid :label (tr "workspace.options.stroke.solid") :id "solid" :icon "stroke-solid"}
{:value :dotted :label (tr "workspace.options.stroke.dotted") :id "dotted" :icon "stroke-dotted"}
{:value :dashed :label (tr "workspace.options.stroke.dashed") :id "dashed" :icon "stroke-dashed"}
{:value :mixed :label (tr "workspace.options.stroke.mixed") :id "mixed" :icon "stroke-mixed"}]))
on-style-change
(mf/use-fn
@@ -212,8 +213,8 @@
:on-blur on-blur}]
;; Stroke Width, Alignment & Style
[:div {:class (stl/css :stroke-options)}
(if token-numeric-inputs
(if token-numeric-inputs
[:div {:class (stl/css :stroke-options-tokens)}
[:> numeric-input-wrapper* {:on-change on-width-change
:on-detach on-detach-token-width
:icon i/stroke-size
@@ -225,7 +226,23 @@
:property (tr "workspace.options.stroke-width")
:applied-token (get applied-tokens :stroke-width)
:value stroke-width}]
[:> select* {:default-selected (d/name stroke-alignment)
:options stroke-alignment-options
:variant "icon-only"
:data-testid "stroke.alignment"
:wrapper-class (stl/css :stroke-align-icon-select)
:on-change on-alignment-change}]
(when-not disable-stroke-style
[:> select* {:default-selected (d/name stroke-style)
:options stroke-style-options
:wrapper-class (stl/css :stroke-style-icon-select)
:data-testid "stroke.style"
:variant "icon-only"
:dropdown-alignment :right
:on-change on-style-change}])]
[:div {:class (stl/css :stroke-options)}
[:div {:class (stl/css :stroke-width-input)
:title (tr "workspace.options.stroke-width")}
[:> icon* {:icon-id i/stroke-size
@@ -236,20 +253,19 @@
:on-change on-width-change
:on-focus on-focus
:select-on-focus select-on-focus
:on-blur on-blur}]])
:on-blur on-blur}]]
[:div {:class (stl/css :stroke-alignment-select)
:data-testid "stroke.alignment"}
[:& select {:default-value stroke-alignment
:options stroke-alignment-options
:on-change on-alignment-change}]]
[:div {:class (stl/css :stroke-alignment-select)
:data-testid "stroke.alignment"}
[:& select {:default-value stroke-alignment
:options stroke-alignment-options
:on-change on-alignment-change}]]
(when-not disable-stroke-style
[:div {:class (stl/css :stroke-style-select)
:data-testid "stroke.style"}
[:& select {:default-value stroke-style
:options stroke-style-options
:on-change on-style-change}]])]
(when-not disable-stroke-style
[:div {:class (stl/css :stroke-style-select)
:data-testid "stroke.style"}
[:& select {:default-value stroke-style
:options stroke-style-options
:on-change on-style-change}]])])
;; Stroke Caps
(when show-caps

View File

@@ -38,17 +38,12 @@
.stroke-width-input {
grid-column: span 2;
// TODO replace with numeric-input* from DS
@extend .input-element;
@include t.use-typography("body-small");
padding-inline-start: var(--sp-xs);
}
.numeric-input-wrapper {
grid-column: span 2;
}
.stroke-alignment-select {
grid-column: span 3;
}
@@ -62,3 +57,18 @@
grid-template-columns: 1fr auto 1fr;
column-gap: var(--sp-xs);
}
.stroke-options-tokens {
@include sidebar.option-grid-structure;
grid-template-columns: var(--3-columns-width) var(--grid-exception-input-width-small) var(
--grid-exception-input-width-small
);
}
.stroke-align-icon-select {
--dropdown-width: var(--4-columns-width);
}
.stroke-style-icon-select {
--dropdown-width: var(--4-columns-width);
}

View File

@@ -6845,6 +6845,10 @@ msgstr "Selected layers"
msgid "workspace.options.layer-options.toggle-layer"
msgstr "Toggle layer visibility"
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs:255
msgid "workspace.options.layer-options.layer-section"
msgstr "Layer menu section"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
#, unused
msgid "workspace.options.layout-item.advanced-ops"
@@ -7029,6 +7033,10 @@ msgstr "Collapse independent radius"
msgid "workspace.options.radius.show-single-corners"
msgstr "Show independent radius"
#: src/app/main/ui/workspace/sidebar/options/menus/border_radius.cljs:341
msgid "workspace.options.radius.radius-section"
msgstr "Border radius section"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:191
msgid "workspace.options.recent-fonts"
msgstr "Recent"

View File

@@ -6762,6 +6762,10 @@ msgstr "Capas seleccionadas"
msgid "workspace.options.layer-options.toggle-layer"
msgstr "Mostrar/ocultar capa"
#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs:255
msgid "workspace.options.layer-options.layer-section"
msgstr "Sección del menú de capas"
#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs
#, unused
msgid "workspace.options.layout-item.advanced-ops"
@@ -6946,6 +6950,10 @@ msgstr "Colapsar radios individuales"
msgid "workspace.options.radius.show-single-corners"
msgstr "Mostrar radios individuales"
#: src/app/main/ui/workspace/sidebar/options/menus/border_radius.cljs:341
msgid "workspace.options.radius.radius-section"
msgstr "Sección de radios de borde"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:191
msgid "workspace.options.recent-fonts"
msgstr "Recientes"