🎉 Replace font family form (#7825)

This commit is contained in:
Eva Marco
2025-12-01 11:17:25 +01:00
committed by GitHub
parent 456afe46de
commit efe74e62e8
31 changed files with 968 additions and 221 deletions

View File

@@ -1070,6 +1070,7 @@ test.describe("Tokens: Apply token", () => {
// Fill in values for all fields and verify they persist when switching tabs
await fontSizeField.fill("16");
await expect(saveButton).toBeEnabled();
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
const letterSpacingField =

View File

@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
[app.main.ui.formats :as fmt]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
@@ -46,7 +47,7 @@
:disabled disabled)}
(if (some? icon)
[:span {:class icon-class} icon]
[:> icon* {:icon-id icon :class icon-class :aria-hidden true}]
[:span {:class (stl/css :title-name)} value])
[:input {:id id

View File

@@ -26,14 +26,19 @@
[:max-length {:optional true} :int]
[:variant {:optional true} [:maybe [:enum "seamless" "dense" "comfortable"]]]
[:hint-message {:optional true} [:maybe :string]]
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]])
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]
[:hint-formated {:optional true} :boolean]])
(mf/defc input*
{::mf/forward-ref true
::mf/schema schema:input}
[{:keys [id class label is-optional type max-length variant hint-message hint-type] :rest props} ref]
[{:keys [id class label is-optional type max-length variant hint-message hint-type hint-formated] :rest props} ref]
(let [id (or id (mf/use-id))
variant (d/nilv variant "dense")
hint-class (if (and (not= "error" hint-type)
hint-formated)
(stl/css :hint-formated)
"")
is-optional (d/nilv is-optional false)
type (d/nilv type "text")
max-length (d/nilv max-length max-input-length)
@@ -56,6 +61,7 @@
[:> input-field* props]
(when has-hint
[:> hint-message* {:id id
:class hint-class
:message hint-message
:type hint-type}])]))

View File

@@ -12,3 +12,7 @@
gap: var(--sp-xs);
inline-size: 100%;
}
.hint-formated {
white-space: pre;
}

View File

@@ -34,10 +34,9 @@
(let [value (-> event dom/get-target dom/get-input-value)]
(fm/on-input-change form input-name value true))))
props
(mf/spread-props props {:on-change on-change
:default-value value})
:value value})
props
(if (and error touched?)

View File

@@ -418,11 +418,11 @@
[:& radio-buttons {:selected color-style
:on-change toggle-token-color
:name "color-style"}
[:& radio-button {:icon deprecated-icon/swatches
[:& radio-button {:icon i/swatches
:value :direct-color
:title (tr "labels.color")
:id "opt-color"}]
[:& radio-button {:icon deprecated-icon/tokens
[:& radio-button {:icon i/tokens
:value :token-color
:title (tr "workspace.colorpicker.color-tokens")
:id "opt-token-color"}]])]

View File

@@ -28,7 +28,6 @@
[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 h]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.sidebar.assets.groups :as grp]
[app.util.dom :as dom]
@@ -567,11 +566,11 @@
[:& radio-buttons {:selected (if is-listing-thumbs "grid" "list")
:on-change toggle-list-style
:name "listing-style"}
[:& radio-button {:icon deprecated-icon/view-as-list
[:& radio-button {:icon i/view-as-list
:value "list"
:title (tr "workspace.assets.list-view")
:id "opt-list"}]
[:& radio-button {:icon deprecated-icon/flex-grid
[:& radio-button {:icon i/flex-grid
:value "grid"
:title (tr "workspace.assets.grid-view")
:id "opt-grid"}]]])

View File

@@ -13,6 +13,7 @@
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@@ -99,10 +100,10 @@
:name "frame-orientation"
:wide true
:class (stl/css :radio-buttons)}
[:& radio-button {:icon deprecated-icon/size-vertical
[:& radio-button {:icon i/size-vertical
:value "vertical"
:id "size-vertical"}]
[:& radio-button {:icon deprecated-icon/size-horizontal
[:& radio-button {:icon i/size-horizontal
:value "horizontal"
:id "size-horizontal"}]]]))

View File

@@ -16,6 +16,7 @@
[app.main.features :as features]
[app.main.store :as st]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon]
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
@@ -78,22 +79,22 @@
:class (stl/css :boolean-radio-btn)
:on-change on-change
:name "bool-options"}
[:& radio-button {:icon deprecated-icon/boolean-union
[:& radio-button {:icon i/boolean-union
:value "union"
:disabled disabled-bool-btns
:title (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :bool-union) ")")
:id "bool-opt-union"}]
[:& radio-button {:icon deprecated-icon/boolean-difference
[:& radio-button {:icon i/boolean-difference
:value "difference"
:disabled disabled-bool-btns
:title (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :bool-difference) ")")
:id "bool-opt-differente"}]
[:& radio-button {:icon deprecated-icon/boolean-intersection
[:& radio-button {:icon i/boolean-intersection
:value "intersection"
:disabled disabled-bool-btns
:title (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
:id "bool-opt-intersection"}]
[:& radio-button {:icon deprecated-icon/boolean-exclude
[:& radio-button {:icon i/boolean-exclude
:value "exclude"
:disabled disabled-bool-btns
:title (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")

View File

@@ -42,7 +42,6 @@
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.sidebar.options.menus.variants-help-modal]
[app.util.debug :as dbg]
@@ -798,10 +797,10 @@
[:& radio-buttons {:selected (if (:listing-thumbs? filters) "grid" "list")
:on-change toggle-list-style
:name "swap-listing-style"}
[:& radio-button {:icon deprecated-icon/view-as-list
[:& radio-button {:icon i/view-as-list
:value "list"
:id "swap-opt-list"}]
[:& radio-button {:icon deprecated-icon/flex-grid
[:& radio-button {:icon i/flex-grid
:value "grid"
:id "swap-opt-grid"}]]]

View File

@@ -206,7 +206,7 @@
:data-value "bottom"
:on-click on-constraint-button-clicked}
[:span {:class (stl/css :resalted-area)}]]]]
[:div {:class (stl/css :contraints-selects)}
[:div {:class (stl/css :constraints-selects)}
[:div {:class (stl/css :horizontal-select) :data-testid "constraint-h-select"}
[:& select
{:default-value (if (not= constraints-h :multiple) (d/nilv (d/name constraints-h) "scale") "")

View File

@@ -120,7 +120,9 @@
}
.constraints-selects {
@include deprecated.flexColumn;
display: flex;
flex-direction: column;
gap: 4px;
}
.horizontal-select,

View File

@@ -18,6 +18,7 @@
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
@@ -52,29 +53,29 @@
:name (dm/str "flex-align-items-" type)}
[:& radio-button {:value "start"
:icon (if is-col?
deprecated-icon/align-self-row-left
deprecated-icon/align-self-column-top)
i/align-self-row-left
i/align-self-column-top)
:title "Align self start"
:id (dm/str "align-self-start-" type)}]
[:& radio-button {:value "center"
:icon (if is-col?
deprecated-icon/align-self-row-center
deprecated-icon/align-self-column-center)
i/align-self-row-center
i/align-self-column-center)
:title "Align self center"
:id (dm/str "align-self-center-" type)}]
[:& radio-button {:value "end"
:icon (if is-col?
deprecated-icon/align-self-row-right
deprecated-icon/align-self-column-bottom)
i/align-self-row-right
i/align-self-column-bottom)
:title "Align self end"
:id (dm/str "align-self-end-" type)}]
[:& radio-button {:value "stretch"
:icon (if is-col?
deprecated-icon/align-self-row-stretch
deprecated-icon/align-self-column-stretch)
i/align-self-row-stretch
i/align-self-column-stretch)
:title "Align self stretch"
:id (dm/str "align-self-stretch-" type)}]]]))

View File

@@ -645,19 +645,19 @@
[:& radio-buttons {:selected (d/name direction)
:on-change change-direction
:name "animation-direction"}
[:& radio-button {:icon deprecated-icon/column
[:& radio-button {:icon i/column
:icon-class (stl/css :right)
:value "right"
:id "animation-right"}]
[:& radio-button {:icon deprecated-icon/column
[:& radio-button {:icon i/column
:icon-class (stl/css :left)
:id "animation-left"
:value "left"}]
[:& radio-button {:icon deprecated-icon/column
[:& radio-button {:icon i/column
:icon-class (stl/css :down)
:id "animation-down"
:value "down"}]
[:& radio-button {:icon deprecated-icon/column
[:& radio-button {:icon i/column
:icon-class (stl/css :up)
:id "animation-up"
:value "up"}]]]])

View File

@@ -42,10 +42,10 @@
(defn- dir-icons-refactor
[val]
(case val
:row deprecated-icon/grid-row
:row-reverse deprecated-icon/row-reverse
:column deprecated-icon/column
:column-reverse deprecated-icon/column-reverse))
:row i/grid-row
:row-reverse i/row-reverse
:column i/column
:column-reverse i/column-reverse))
(mf/defc numeric-input-wrapper*
@@ -111,63 +111,63 @@
:align-items
(if column?
(case val
:start deprecated-icon/align-items-column-start
:end deprecated-icon/align-items-column-end
:center deprecated-icon/align-items-column-center)
:start i/align-items-column-start
:end i/align-items-column-end
:center i/align-items-column-center)
(case val
:start deprecated-icon/align-items-row-start
:end deprecated-icon/align-items-row-end
:center deprecated-icon/align-items-row-center))
:start i/align-items-row-start
:end i/align-items-row-end
:center i/align-items-row-center))
:justify-content
(if column?
(case val
:start deprecated-icon/justify-content-column-start
:end deprecated-icon/justify-content-column-end
:center deprecated-icon/justify-content-column-center
:space-around deprecated-icon/justify-content-column-around
:space-evenly deprecated-icon/justify-content-column-evenly
:space-between deprecated-icon/justify-content-column-between)
:start i/justify-content-column-start
:end i/justify-content-column-end
:center i/justify-content-column-center
:space-around i/justify-content-column-around
:space-evenly i/justify-content-column-evenly
:space-between i/justify-content-column-between)
(case val
:start deprecated-icon/justify-content-row-start
:end deprecated-icon/justify-content-row-end
:center deprecated-icon/justify-content-row-center
:space-around deprecated-icon/justify-content-row-around
:space-evenly deprecated-icon/justify-content-row-evenly
:space-between deprecated-icon/justify-content-row-between))
:start i/justify-content-row-start
:end i/justify-content-row-end
:center i/justify-content-row-center
:space-around i/justify-content-row-around
:space-evenly i/justify-content-row-evenly
:space-between i/justify-content-row-between))
:align-content
(if column?
(case val
:start deprecated-icon/align-content-column-start
:end deprecated-icon/align-content-column-end
:center deprecated-icon/align-content-column-center
:space-around deprecated-icon/align-content-column-around
:space-evenly deprecated-icon/align-content-column-evenly
:space-between deprecated-icon/align-content-column-between
:start i/align-content-column-start
:end i/align-content-column-end
:center i/align-content-column-center
:space-around i/align-content-column-around
:space-evenly i/align-content-column-evenly
:space-between i/align-content-column-between
:stretch nil)
(case val
:start deprecated-icon/align-content-row-start
:end deprecated-icon/align-content-row-end
:center deprecated-icon/align-content-row-center
:space-around deprecated-icon/align-content-row-around
:space-evenly deprecated-icon/align-content-row-evenly
:space-between deprecated-icon/align-content-row-between
:start i/align-content-row-start
:end i/align-content-row-end
:center i/align-content-row-center
:space-around i/align-content-row-around
:space-evenly i/align-content-row-evenly
:space-between i/align-content-row-between
:stretch nil))
:align-self
(if column?
(case val
:auto deprecated-icon/remove-icon
:start deprecated-icon/align-self-row-left
:end deprecated-icon/align-self-row-right
:center deprecated-icon/align-self-row-center)
:auto i/remove
:start i/align-self-row-left
:end i/align-self-row-right
:center i/align-self-row-center)
(case val
:auto deprecated-icon/remove-icon
:start deprecated-icon/align-self-column-top
:end deprecated-icon/align-self-column-bottom
:center deprecated-icon/align-self-column-center))))
:auto i/remove
:start i/align-self-column-top
:end i/align-self-column-bottom
:center i/align-self-column-center))))
(defn get-layout-grid-icon
[type val ^boolean column?]
@@ -175,32 +175,32 @@
:align-items
(if column?
(case val
:auto deprecated-icon/remove-icon
:start deprecated-icon/align-self-row-left
:end deprecated-icon/align-self-row-right
:center deprecated-icon/align-self-row-center)
:auto i/remove
:start i/align-self-row-left
:end i/align-self-row-right
:center i/align-self-row-center)
(case val
:auto deprecated-icon/remove-icon
:start deprecated-icon/align-self-column-top
:end deprecated-icon/align-self-column-bottom
:center deprecated-icon/align-self-column-center))
:auto i/remove
:start i/align-self-column-top
:end i/align-self-column-bottom
:center i/align-self-column-center))
:justify-items
(if (not column?)
(case val
:start deprecated-icon/align-content-column-start
:center deprecated-icon/align-content-column-center
:end deprecated-icon/align-content-column-end
:space-around deprecated-icon/align-content-column-around
:space-between deprecated-icon/align-content-column-between
:stretch deprecated-icon/align-content-column-stretch)
:start i/align-content-column-start
:center i/align-content-column-center
:end i/align-content-column-end
:space-around i/align-content-column-around
:space-between i/align-content-column-between
:stretch i/align-content-column-stretch)
(case val
:start deprecated-icon/align-content-row-start
:center deprecated-icon/align-content-row-center
:end deprecated-icon/align-content-row-end
:space-around deprecated-icon/align-content-row-around
:space-between deprecated-icon/align-content-row-between
:stretch deprecated-icon/align-content-row-stretch))))
:start i/align-content-row-start
:center i/align-content-row-center
:end i/align-content-row-end
:space-around i/align-content-row-around
:space-between i/align-content-row-between
:stretch i/align-content-row-stretch))))
(mf/defc direction-row-flex
{::mf/props :obj

View File

@@ -455,10 +455,10 @@
:name "frame-orientation"
:wide true
:class (stl/css :radio-buttons)}
[:& radio-button {:icon deprecated-icon/size-vertical
[:& radio-button {:icon i/size-vertical
:value "vert"
:id "size-vertical"}]
[:& radio-button {:icon deprecated-icon/size-horizontal
[:& radio-button {:icon i/size-horizontal
:value "horiz"
:id "size-horizontal"}]]
[:> icon-button*

View File

@@ -53,19 +53,19 @@
[:& radio-button {:value "left"
:id "text-align-left"
:title (tr "workspace.options.text-options.text-align-left")
:icon deprecated-icon/text-align-left}]
:icon i/text-align-left}]
[:& radio-button {:value "center"
:id "text-align-center"
:title (tr "workspace.options.text-options.text-align-center")
:icon deprecated-icon/text-align-center}]
:icon i/text-align-center}]
[:& radio-button {:value "right"
:id "text-align-right"
:title (tr "workspace.options.text-options.text-align-right")
:icon deprecated-icon/text-align-right}]
:icon i/text-align-right}]
[:& radio-button {:value "justify"
:id "text-align-justify"
:title (tr "workspace.options.text-options.text-align-justify")
:icon deprecated-icon/text-justify}]]]))
:icon i/text-justify}]]]))
(mf/defc text-direction-options
[{:keys [values on-change on-blur] :as props}]
@@ -88,12 +88,12 @@
:type "checkbox"
:id "ltr-text-direction"
:title (tr "workspace.options.text-options.direction-ltr")
:icon deprecated-icon/text-ltr}]
:icon i/text-ltr}]
[:& radio-button {:value "rtl"
:type "checkbox"
:id "rtl-text-direction"
:title (tr "workspace.options.text-options.direction-rtl")
:icon deprecated-icon/text-rtl}]]]))
:icon i/text-rtl}]]]))
(mf/defc vertical-align
[{:keys [values on-change on-blur] :as props}]
@@ -113,15 +113,15 @@
[:& radio-button {:value "top"
:id "vertical-text-align-top"
:title (tr "workspace.options.text-options.align-top")
:icon deprecated-icon/text-top}]
:icon i/text-top}]
[:& radio-button {:value "center"
:id "vertical-text-align-center"
:title (tr "workspace.options.text-options.align-middle")
:icon deprecated-icon/text-middle}]
:icon i/text-middle}]
[:& radio-button {:value "bottom"
:id "vertical-text-align-bottom"
:title (tr "workspace.options.text-options.align-bottom")
:icon deprecated-icon/text-bottom}]]]))
:icon i/text-bottom}]]]))
(mf/defc grow-options
[{:keys [ids values on-blur] :as props}]
@@ -150,15 +150,15 @@
[:& radio-button {:value "fixed"
:id "text-fixed-grow"
:title (tr "workspace.options.text-options.grow-fixed")
:icon deprecated-icon/text-fixed}]
:icon i/text-fixed}]
[:& radio-button {:value "auto-width"
:id "text-auto-width-grow"
:title (tr "workspace.options.text-options.grow-auto-width")
:icon deprecated-icon/text-auto-width}]
:icon i/text-auto-width}]
[:& radio-button {:value "auto-height"
:id "text-auto-height-grow"
:title (tr "workspace.options.text-options.grow-auto-height")
:icon deprecated-icon/text-auto-height}]]]))
:icon i/text-auto-height}]]]))
(mf/defc text-decoration-options
[{:keys [values on-change on-blur] :as props}]
@@ -180,12 +180,12 @@
:type "checkbox"
:id "underline-text-decoration"
:title (tr "workspace.options.text-options.underline" (sc/get-tooltip :underline))
:icon deprecated-icon/text-underlined}]
:icon i/text-underlined}]
[:& radio-button {:value "line-through"
:type "checkbox"
:id "line-through-text-decoration"
:title (tr "workspace.options.text-options.strikethrough" (sc/get-tooltip :line-through))
:icon deprecated-icon/text-stroked}]]]))
:icon i/text-stroked}]]]))
(mf/defc text-menu
{::mf/wrap [mf/memo]}

View File

@@ -26,6 +26,7 @@
[app.main.ui.components.search-bar :refer [search-bar*]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@@ -418,17 +419,17 @@
[:& radio-buttons {:selected text-transform
:on-change handle-change
:name "text-transform"}
[:& radio-button {:icon deprecated-icon/text-uppercase
[:& radio-button {:icon i/text-uppercase
:type "checkbox"
:title (tr "inspect.attributes.typography.text-transform.uppercase")
:value "uppercase"
:id "text-transform-uppercase"}]
[:& radio-button {:icon deprecated-icon/text-mixed
[:& radio-button {:icon i/text-mixed
:type "checkbox"
:value "capitalize"
:title (tr "inspect.attributes.typography.text-transform.capitalize")
:id "text-transform-capitalize"}]
[:& radio-button {:icon deprecated-icon/text-lowercase
[:& radio-button {:icon i/text-lowercase
:type "checkbox"
:title (tr "inspect.attributes.typography.text-transform.lowercase")
:value "lowercase"

View File

@@ -52,8 +52,6 @@
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
@@ -82,7 +80,7 @@
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens
(mf/with-memo [tokens]
(mf/with-memo [tokens token]
;; Ensure that the resolved value uses the currently editing token
;; even if the name has been overriden by a token with the same name
;; in another set below.

View File

@@ -52,8 +52,6 @@
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
@@ -82,7 +80,7 @@
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens
(mf/with-memo [tokens]
(mf/with-memo [tokens token]
;; Ensure that the resolved value uses the currently editing token
;; even if the name has been overriden by a token with the same name
;; in another set below.

View File

@@ -21,7 +21,7 @@
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
[beicon.v2.core :as rx]
[clojure.core :as c]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@@ -51,10 +51,6 @@
(let [form (mf/use-ctx fc/context)
input-name name
resolved-input-name
(mf/with-memo [input-name]
(keyword (str "resolved-" (c/name input-name))))
touched?
(and (contains? (:data @form) input-name)
(get-in @form [:touched input-name]))
@@ -125,6 +121,7 @@
:value (or value "")
:hint-message (:message hint)
:slot-end font-selector-button
:variant "comfortable"
:hint-type (:type hint)})
props
@@ -133,7 +130,7 @@
:hint-message (:message error)})
props)]
(mf/with-effect [resolve-stream tokens token input-name]
(mf/with-effect [resolve-stream tokens token input-name touched?]
(let [subs (->> resolve-stream
(rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token))
@@ -142,16 +139,154 @@
(fn [error]
((:error/fn error) (:error/value error))))))
(rx/subs! (fn [{:keys [error value]}]
(when touched?
(if error
(do
(swap! form assoc-in [:errors input-name] {:message error})
(swap! form assoc-in [:errors resolved-input-name] {:message error})
(swap! form update :data dissoc resolved-input-name)
(swap! form assoc-in [:extra-errors input-name] {:message error})
(reset! hint* {:message error :type "error"}))
(let [message (tr "workspace.tokens.resolved-value" (cto/join-font-family value))]
(swap! form update :errors dissoc input-name resolved-input-name)
(swap! form update :data assoc resolved-input-name value)
(reset! hint* {:message message :type "hint"}))))))]
(let [message (tr "workspace.tokens.resolved-value" value)]
(swap! form update :extra-errors dissoc input-name)
(reset! hint* {:message message :type "hint"})))))))]
(fn []
(rx/dispose! subs))))
[:*
[:> input* props]
(when font-selector-open?
[:div {:class (stl/css :font-select-wrapper)}
[:> font-selector* {:current-font font
:on-select on-select-font
:on-close on-close-font-selector
:full-size true}]])]))
(defn- on-composite-combobox-token-change
([form field value]
(on-composite-combobox-token-change form field value false))
([form field value trim?]
(letfn [(clean-errors [errors]
(-> errors
(dissoc field)
(not-empty)))]
(swap! form (fn [state]
(-> state
(assoc-in [:data :value field] (if trim? (str/trim value) value))
(update :errors clean-errors)
(update :extra-errors clean-errors)))))))
(mf/defc font-picker-composite-combobox*
[{:keys [token tokens name] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
error
(get-in @form [:errors :value input-name])
value
(get-in @form [:data :value input-name] "")
font (fonts/find-font-family value)
resolve-stream
(mf/with-memo [token]
(if-let [value (get-in token [:value input-name])]
(rx/behavior-subject value)
(rx/subject)))
hint*
(mf/use-state {})
hint
(deref hint*)
font-selector-open* (mf/use-state false)
font-selector-open? (deref font-selector-open*)
on-click-dropdown-button
(mf/use-fn
(mf/deps font-selector-open?)
(fn [e]
(dom/prevent-default e)
(reset! font-selector-open* (not font-selector-open?))))
font-selector-button
(mf/html
[:> icon-button*
{:on-click on-click-dropdown-button
:aria-label (tr "workspace.tokens.token-font-family-select")
:icon i/arrow-down
:variant "action"
:type "button"}])
on-close-font-selector
(mf/use-fn
(fn []
(reset! font-selector-open* false)))
on-select-font
(mf/use-fn
(mf/deps font)
(fn [{:keys [family] :as font}]
(when (not= value family)
(on-composite-combobox-token-change form input-name family true)
(rx/push! resolve-stream family))))
on-change
(mf/use-fn
(mf/deps resolve-stream input-name)
(fn [event]
(let [value (-> event dom/get-target dom/get-input-value)]
(on-composite-combobox-token-change form input-name value false)
(rx/push! resolve-stream value))))
props
(mf/spread-props props {:on-change on-change
;; TODO: Review this value vs default-value
:value (or value "")
:hint-message (:message hint)
:slot-end font-selector-button
:variant "comfortable"
:hint-type (:type hint)})
props
(if error
(mf/spread-props props {:hint-type "error"
:hint-message (:message error)})
props)]
(mf/with-effect [resolve-stream tokens token input-name]
(let [subs (->> resolve-stream
(rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token))
(rx/map (fn [result]
(d/update-when result :error
(fn [error]
((:error/fn error) (:error/value error))))))
(rx/subs!
(fn [{:keys [error value]}]
(cond
(and error (str/empty? (:error/value error)))
(do
(swap! form update-in [:errors :value] dissoc input-name)
(swap! form update-in [:data :value] dissoc input-name)
(swap! form update :extra-errors dissoc :value)
(reset! hint* {}))
(some? error)
(let [error' (:message error)]
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
(reset! hint* {:message error' :type "error"}))
:else
(let [message (tr "workspace.tokens.resolved-value" value)
input-value (get-in @form [:data :value input-name] "")]
(swap! form update :errors dissoc :value)
(swap! form update :extra-errors dissoc :value)
(if (or (empty? value) (= input-value value))
(reset! hint* {})
(reset! hint* {:message message :type "hint"})))))))]
(fn []
(rx/dispose! subs))))

View File

@@ -52,8 +52,6 @@
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
@@ -82,7 +80,7 @@
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens
(mf/with-memo [tokens]
(mf/with-memo [tokens token]
;; Ensure that the resolved value uses the currently editing token
;; even if the name has been overriden by a token with the same name
;; in another set below.

View File

@@ -46,8 +46,6 @@
[:value ::sm/text]
[:resolved-value ::sm/any]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
@@ -79,7 +77,7 @@
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens
(mf/with-memo [tokens]
(mf/with-memo [tokens token]
;; Ensure that the resolved value uses the currently editing token
;; even if the name has been overriden by a token with the same name
;; in another set below.

View File

@@ -31,7 +31,6 @@
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.colorpicker :as colorpicker]
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
@@ -42,6 +41,7 @@
[app.main.ui.workspace.tokens.management.create.input-token-color-bullet :refer [input-token-color-bullet*]]
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token* token-value-hint*]]
[app.main.ui.workspace.tokens.management.create.text-case :as text-case]
[app.main.ui.workspace.tokens.management.create.typography :as typography]
[app.util.dom :as dom]
[app.util.functions :as uf]
[app.util.i18n :refer [tr]]
@@ -736,11 +736,11 @@
:selected (if reference-tab-active? "reference" "composite")
:on-change on-toggle-tab
:name "reference-composite-tab"}
[:& radio-button {:icon deprecated-icon/layers
[:& radio-button {:icon i/layers
:value "composite"
:title (tr "workspace.tokens.individual-tokens")
:id "composite-opt"}]
[:& radio-button {:icon deprecated-icon/tokens
[:& radio-button {:icon i/tokens
:value "reference"
:title (tr "workspace.tokens.use-reference")
:id "reference-opt"}]]]
@@ -975,11 +975,11 @@
:name (str "inset-select-" shadow-idx)}
[:& radio-button {:value "false"
:title "false"
:icon "❌"
:icon i/close
:id (str "inset-default-" shadow-idx)}]
[:& radio-button {:value "true"
:title "true"
:icon "✅"
:icon i/tick
:id (str "inset-false-" shadow-idx)}]]]))
(mf/defc shadow-input*
@@ -1439,11 +1439,12 @@
:validate-token default-validate-token
:tokens-tree-in-selected-set tokens-tree-in-selected-set
:token token})
font-family-props (mf/spread-props props {:validate-token validate-font-family-token})]
font-family-props (mf/spread-props props {:validate-token validate-font-family-token})
typography-props (mf/spread-props props {:validate-token validate-typography-token})]
(case token-type
:color [:> color/form* props]
:typography [:> typography-form* props]
:typography [:> typography/form* typography-props]
:shadow [:> shadow-form* props]
:font-family [:> font-family/form* font-family-props]
:text-case [:> text-case/form* props]

View File

@@ -22,7 +22,6 @@
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
[beicon.v2.core :as rx]
[clojure.core :as c]
[rumext.v2 :as mf]))
(defn- resolve-value
@@ -105,9 +104,6 @@
(let [form (mf/use-ctx fc/context)
input-name name
resolved-input-name
(mf/with-memo [input-name]
(keyword (str "resolved-" (c/name input-name))))
touched?
(and (contains? (:data @form) input-name)
@@ -119,15 +115,12 @@
value
(get-in @form [:data input-name] "")
resolved-value
(get-in @form [:data resolved-input-name] "")
hex (if (tinycolor/valid-color resolved-value)
(tinycolor/->hex-string (tinycolor/valid-color resolved-value))
hex (if (tinycolor/valid-color value)
(tinycolor/->hex-string (tinycolor/valid-color value))
"#8f9da3")
alpha (if (tinycolor/valid-color resolved-value)
(tinycolor/alpha (tinycolor/valid-color resolved-value))
alpha (if (tinycolor/valid-color value)
(tinycolor/alpha (tinycolor/valid-color value))
1)
resolve-stream
@@ -224,16 +217,15 @@
(fn [error]
((:error/fn error) (:error/value error))))))
(rx/subs! (fn [{:keys [error value]}]
(let [touched? (get-in @form [:touched input-name])]
(when touched?
(if error
(do
(swap! form assoc-in [:errors input-name] {:message error})
(swap! form assoc-in [:errors resolved-input-name] {:message error})
(swap! form update :data dissoc resolved-input-name)
(swap! form assoc-in [:extra-errors input-name] {:message error})
(reset! hint* {:message error :type "error"}))
(let [message (tr "workspace.tokens.resolved-value" value)]
(swap! form update :errors dissoc input-name resolved-input-name)
(swap! form update :data assoc resolved-input-name value)
(reset! hint* {:message message :type "hint"}))))))]
(swap! form update :extra-errors dissoc input-name)
(reset! hint* {:message message :type "hint"}))))))))]
(fn []
(rx/dispose! subs))))

View File

@@ -9,13 +9,14 @@
[app.common.data :as d]
[app.common.types.tokens-lib :as ctob]
[app.main.data.style-dictionary :as sd]
[app.main.data.workspace.tokens.format :as dwtf]
[app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.forms :as fc]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
[beicon.v2.core :as rx]
[clojure.core :as c]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- resolve-value
@@ -23,7 +24,6 @@
(let [token
{:value value
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
tokens
(-> tokens
;; Remove previous token when renaming a token
@@ -45,10 +45,6 @@
(let [form (mf/use-ctx fc/context)
input-name name
resolved-input-name
(mf/with-memo [input-name]
(keyword (str "resolved-" (c/name input-name))))
touched?
(and (contains? (:data @form) input-name)
(get-in @form [:touched input-name]))
@@ -61,8 +57,8 @@
resolve-stream
(mf/with-memo [token]
(if-let [value (:value token)]
(rx/behavior-subject value)
(if (contains? token :value)
(rx/behavior-subject (:value token))
(rx/subject)))
hint*
@@ -82,10 +78,9 @@
props
(mf/spread-props props {:on-change on-change
:default-value value
:hint-message (:message hint)
:variant "comfortable"
:hint-message (:message hint)
:hint-type (:type hint)})
props
(if (and error touched?)
(mf/spread-props props {:hint-type "error"
@@ -101,18 +96,116 @@
(fn [error]
((:error/fn error) (:error/value error))))))
(rx/subs! (fn [{:keys [error value]}]
(let [touched? (get-in @form [:touched input-name])]
(when touched?
(if error
(do
(swap! form assoc-in [:errors input-name] {:message error})
(swap! form assoc-in [:errors resolved-input-name] {:message error})
(swap! form update :data dissoc resolved-input-name)
(swap! form assoc-in [:extra-errors input-name] {:message error})
(reset! hint* {:message error :type "error"}))
(let [message (tr "workspace.tokens.resolved-value" value)]
(swap! form update :errors dissoc input-name resolved-input-name)
(swap! form update :data assoc resolved-input-name value)
(reset! hint* {:message message :type "hint"}))))))]
(swap! form update :extra-errors dissoc input-name)
(reset! hint* {:message message :type "hint"}))))))))]
(fn []
(rx/dispose! subs))))
[:> input* props]))
(defn- on-composite-input-token-change
([form field value]
(on-composite-input-token-change form field value false))
([form field value trim?]
(letfn [(clean-errors [errors]
(-> errors
(dissoc field)
(not-empty)))]
(swap! form (fn [state]
(-> state
(assoc-in [:data :value field] (if trim? (str/trim value) value))
(update :errors clean-errors)
(update :extra-errors clean-errors)))))))
(mf/defc token-composite-value-input*
[{:keys [name tokens token] :rest props}]
(let [form (mf/use-ctx fc/context)
input-name name
error
(get-in @form [:errors :value input-name])
value
(get-in @form [:data :value input-name] "")
resolve-stream
(mf/with-memo [token]
(if-let [value (get-in token [:value input-name])]
(rx/behavior-subject value)
(rx/subject)))
hint*
(mf/use-state {})
hint
(deref hint*)
on-change
(mf/use-fn
(mf/deps resolve-stream input-name)
(fn [event]
(let [value (-> event dom/get-target dom/get-input-value)]
(on-composite-input-token-change form input-name value true)
(rx/push! resolve-stream value))))
props
(mf/spread-props props {:on-change on-change
:default-value value
:variant "comfortable"
:hint-message (:message hint)
:hint-type (:type hint)})
props
(if error
(mf/spread-props props {:hint-type "error"
:hint-message (:message error)})
props)
props (if (and (not error) (= input-name :reference))
(mf/spread-props props {:hint-formated true})
props)]
(mf/with-effect [resolve-stream tokens token input-name]
(let [subs (->> resolve-stream
(rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token))
(rx/map (fn [result]
(d/update-when result :error
(fn [error]
(assoc error :message ((:error/fn error) (:error/value error)))))))
(rx/subs!
(fn [{:keys [error value]}]
(cond
(and error (str/empty? (:error/value error)))
(do
(swap! form update-in [:errors :value] dissoc input-name)
(swap! form update-in [:data :value] dissoc input-name)
(swap! form update :extra-errors dissoc :value)
(reset! hint* {}))
(some? error)
(let [error' (:message error)]
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
(reset! hint* {:message error' :type "error"}))
:else
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
input-value (get-in @form [:data :value input-name] "")]
(swap! form update :errors dissoc :value)
(swap! form update :extra-errors dissoc :value)
(if (= input-value (str value))
(reset! hint* {})
(reset! hint* {:message message :type "hint"})))))))]
(fn []
(rx/dispose! subs))))
[:> input* props]))

View File

@@ -52,8 +52,6 @@
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
@@ -82,7 +80,7 @@
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens
(mf/with-memo [tokens]
(mf/with-memo [tokens token]
;; Ensure that the resolved value uses the currently editing token
;; even if the name has been overriden by a token with the same name
;; in another set below.

View File

@@ -0,0 +1,427 @@
;; 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.management.create.typography
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.files.tokens :as cft]
[app.common.schema :as sm]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.main.constants :refer [max-input-length]]
[app.main.data.modal :as modal]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.tokens.propagation :as dwtp]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.forms :as forms]
[app.main.ui.workspace.tokens.management.create.combobox-token-fonts :refer [font-picker-composite-combobox*]]
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [token-composite-value-input*]]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
[app.util.keyboard :as k]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc composite-form*
[{:keys [token tokens] :as props}]
(let [letter-spacing-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :letter-spacing
:value (cto/join-font-family (get value :letter-spacing))}
{:type :letter-spacing}))
font-family-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :font-family
:value (get value :font-family)}
{:type :font-family}))
font-size-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :font-size
:value (get value :font-size)}
{:type :font-size}))
font-weight-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :font-weight
:value (get value :font-weight)}
{:type :font-weight}))
;; TODO: Review this type
line-height-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :number
:value (get value :line-height)}
{:type :number}))
text-case-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :text-case
:value (get value :text-case)}
{:type :text-case}))
text-decoration-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :text-decoration
:value (get value :text-decoration)}
{:type :text-decoration}))]
[:*
[:div {:class (stl/css :input-row)}
[:> font-picker-composite-combobox*
{:icon i/text-font-family
:placeholder (tr "workspace.tokens.token-font-family-value-enter")
:aria-label (tr "workspace.tokens.token-font-family-value")
:name :font-family
:token font-family-sub-token
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:aria-label "Font Size"
:icon i/text-font-size
:placeholder (tr "workspace.tokens.font-size-value-enter")
:name :font-size
:token font-size-sub-token
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:aria-label "Font Weight"
:icon i/text-font-weight
:placeholder (tr "workspace.tokens.font-weight-value-enter")
:name :font-weight
:token font-weight-sub-token
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:aria-label "Line Height"
:icon i/text-lineheight
:placeholder (tr "workspace.tokens.line-height-value-enter")
:name :line-height
:token line-height-sub-token
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:aria-label "Letter Spacing"
:icon i/text-letterspacing
:placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")
:name :letter-spacing
:token letter-spacing-sub-token
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:aria-label "Text Case"
:icon i/text-mixed
:placeholder (tr "workspace.tokens.text-case-value-enter")
:name :text-case
:token text-case-sub-token
:tokens tokens}]]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:aria-label "Text Decoration"
:icon i/text-underlined
:placeholder (tr "workspace.tokens.text-decoration-value-enter")
:name :text-decoration
:token text-decoration-sub-token
:tokens tokens}]]]))
(mf/defc reference-form*
[{:keys [token tokens] :as props}]
[:div {:class (stl/css :input-row)}
[:> token-composite-value-input*
{:placeholder (tr "workspace.tokens.reference-composite")
:aria-label (tr "labels.reference")
:icon i/text-typography
:name :reference
:token token
:tokens tokens}]])
(defn- make-schema
[tokens-tree active-tab]
(sm/schema
[:and
[:map
[:name
[:and
[:string {:min 1 :max 255
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
(sm/update-properties cto/token-name-ref assoc
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value
[:map
[:font-family {:optional true} [:maybe :string]]
[:font-size {:optional true} [:maybe :string]]
[:font-weight {:optional true} [:maybe :string]]
[:line-height {:optional true} [:maybe :string]]
[:letter-spacing {:optional true} [:maybe :string]]
[:text-case {:optional true} [:maybe :string]]
[:text-decoration {:optional true} [:maybe :string]]
(if (= active-tab :reference)
[:reference {:optional false} ::sm/text]
[:reference {:optional true} [:maybe :string]])]]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
[:fn {:error/field [:value :reference]
:error/fn #(tr "workspace.tokens.self-reference")}
(fn [{:keys [name value]}]
(let [reference (get value :reference)]
(if (and reference name)
(not (cto/token-value-self-reference? name reference))
true)))]
[:fn {:error/field [:value :line-height]
:error/fn #(tr "workspace.tokens.composite-line-height-needs-font-size")}
(fn [{:keys [value]}]
(let [line-heigh (get value :line-height)
font-size (get value :font-size)]
(if (and line-heigh (not font-size))
false
true)))]
;; This error does not shown on interface, it's just to avoid saving empty composite tokens
;; We don't need to translate it.
[:fn {:error/fn (fn [_] "At least one composite field must be set")
:error/field :value}
(fn [attrs]
(let [result (reduce-kv (fn [_ _ v]
(if (str/empty? v)
false
(reduced true)))
false
(get attrs :value))]
result))]]))
(mf/defc form*
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
(let [token
(mf/with-memo [token]
(or token {:type :typography}))
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
active-tab (deref active-tab*)
token-type
(get token :type)
token-properties
(dwta/get-token-properties token)
token-title (str/lower (:title token-properties))
tokens
(mf/deref refs/workspace-active-theme-sets-tokens)
tokens
(mf/with-memo [tokens token]
;; Ensure that the resolved value uses the currently editing token
;; even if the name has been overriden by a token with the same name
;; in another set below.
(cond-> tokens
(and (:name token) (:value token))
(assoc (:name token) token)))
schema
(mf/with-memo [tokens-tree-in-selected-set active-tab]
(make-schema tokens-tree-in-selected-set active-tab))
initial
(mf/with-memo [token]
(let [value (:value token)
processed-value
(cond
(string? value)
{:reference value}
(map? value)
(let [value (cond-> value
(:font-family value)
(update :font-family cto/join-font-family))]
(select-keys value
[:font-family
:font-size
:font-weight
:line-height
:letter-spacing
:text-case
:text-decoration]))
:else
{})]
{:name (:name token "")
:value processed-value
:description (:description token "")}))
form
(fm/use-form :schema schema
:initial initial)
warning-name-change?
(not= (get-in @form [:data :name])
(:name initial))
on-toggle-tab
(mf/use-fn
(mf/deps)
(fn [new-tab]
(let [new-tab (keyword new-tab)]
(reset! active-tab* new-tab))))
on-cancel
(mf/use-fn
(fn [e]
(dom/prevent-default e)
(modal/hide!)))
on-delete-token
(mf/use-fn
(mf/deps selected-token-set-id token)
(fn [e]
(dom/prevent-default e)
(modal/hide!)
(st/emit! (dwtl/delete-token selected-token-set-id (:id token)))))
handle-key-down-delete
(mf/use-fn
(mf/deps on-delete-token)
(fn [e]
(when (or (k/enter? e) (k/space? e))
(on-delete-token e))))
handle-key-down-cancel
(mf/use-fn
(mf/deps on-cancel)
(fn [e]
(when (or (k/enter? e) (k/space? e))
(on-cancel e))))
on-submit
(mf/use-fn
(mf/deps validate-token token tokens token-type)
(fn [form _event]
(let [name (get-in @form [:clean-data :name])
description (get-in @form [:clean-data :description])
value (get-in @form [:clean-data :value])]
(->> (validate-token {:token-value (if (contains? value :reference)
(get value :reference)
value)
:token-name name
:token-description description
:prev-token token
:tokens tokens})
(rx/subs!
(fn [valid-token]
(st/emit!
(if is-create
(dwtl/create-token (ctob/make-token {:name name
:type token-type
:value (:value valid-token)
:description description}))
(dwtl/update-token (:id token)
{:name name
:value (:value valid-token)
:description description}))
(dwtp/propagate-workspace-tokens)
(modal/hide))))))))]
[:> forms/form* {:class (stl/css :form-wrapper)
:form form
:on-submit on-submit}
[:div {:class (stl/css :token-rows)}
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
(tr "workspace.tokens.create-token" token-type)]
[:div {:class (stl/css :input-row)}
[:> forms/form-input* {:id "token-name"
:name :name
:label (tr "workspace.tokens.token-name")
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
:max-length max-input-length
:variant "comfortable"
:auto-focus true}]
(when (and warning-name-change? (= action "edit"))
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
[:> context-notification*
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
[:div {:class (stl/css :title-bar)}
[:div {:class (stl/css :title)} (tr "labels.typography")]
[:& radio-buttons {:class (stl/css :listing-options)
:selected (d/name active-tab)
:on-change on-toggle-tab
:name "reference-composite-tab"}
[:& radio-button {:icon i/layers
:value "composite"
:title (tr "workspace.tokens.individual-tokens")
:id "composite-opt"}]
[:& radio-button {:icon i/tokens
:value "reference"
:title (tr "workspace.tokens.use-reference")
:id "reference-opt"}]]]
[:div {:class (stl/css :inputs-wrapper)}
(if (= active-tab :composite)
[:> composite-form* {:token token
:tokens tokens}]
[:> reference-form* {:token token
:tokens tokens}])]
[:div {:class (stl/css :input-row)}
[:> forms/form-input* {:id "token-description"
:name :description
:label (tr "workspace.tokens.token-description")
:placeholder (tr "workspace.tokens.token-description")
:max-length max-input-length
:variant "comfortable"
:is-optional true}]]
[:div {:class (stl/css-case :button-row true
:with-delete (= action "edit"))}
(when (= action "edit")
[:> button* {:on-click on-delete-token
:on-key-down handle-key-down-delete
:class (stl/css :delete-btn)
:type "button"
:icon i/delete
:variant "secondary"}
(tr "labels.delete")])
[:> button* {:on-click on-cancel
:on-key-down handle-key-down-cancel
:type "button"
:id "token-modal-cancel"
:variant "secondary"}
(tr "labels.cancel")]
[:> forms/form-submit* {:variant "primary"
:on-submit on-submit}
(tr "labels.save")]]]]))

View File

@@ -0,0 +1,74 @@
// 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 t;
@use "ds/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
.form-wrapper {
width: $sz-384;
position: relative;
}
.token-rows {
display: flex;
flex-direction: column;
gap: var(--sp-l);
}
.inputs-wrapper {
display: flex;
flex-direction: column;
gap: var(--sp-m);
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
padding-inline-start: var(--sp-m);
}
.input-row {
position: relative;
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.title-bar {
display: grid;
grid-template-columns: 1fr auto;
}
.title {
@include t.use-typography("body-small");
color: var(--color-foreground-primary);
display: flex;
align-items: center;
}
.form-modal-title {
@include t.use-typography("headline-medium");
color: var(--color-foreground-primary);
display: flex;
align-items: center;
}
.button-row {
display: grid;
grid-template-columns: auto auto;
justify-content: end;
gap: var(--sp-m);
padding-block-start: var(--sp-s);
}
.with-delete {
grid-template-columns: 1fr auto auto;
}
.warning-name-change-notification-wrapper {
margin-block-start: var(--sp-l);
}
.delete-btn {
justify-self: start;
}

View File

@@ -25,7 +25,6 @@
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.tokens.sets.lists :as wts]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
@@ -94,11 +93,11 @@
:name name}
[:& radio-button {:id :on
:value :on
:icon deprecated-icon/tick
:icon i/tick
:label ""}]
[:& radio-button {:id :off
:value :off
:icon deprecated-icon/close
:icon i/close
:label ""}]]))
(mf/defc themes-overview

View File

@@ -48,7 +48,11 @@
(let [props (m/properties schema)
tprops (m/type-properties schema)
field (or (first in)
(:error/field props))]
(:error/field props))
field (if (vector? field)
field
[field])]
(if (contains? acc field)
acc
@@ -58,30 +62,30 @@
(or (= type :malli.core/missing-key)
(nil? value))
(assoc acc field {:message (tr "errors.field-missing")})
(assoc-in acc field {:message (tr "errors.field-missing")})
;; --- CHECK on schema props
(contains? props :error/fn)
(assoc acc field (handle-error-fn props problem))
(assoc-in acc field (handle-error-fn props problem))
(contains? props :error/message)
(assoc acc field (handle-error-message props))
(assoc-in acc field (handle-error-message props))
(contains? props :error/code)
(assoc acc field (handle-error-code props))
(assoc-in acc field (handle-error-code props))
;; --- CHECK on type props
(contains? tprops :error/fn)
(assoc acc field (handle-error-fn tprops problem))
(assoc-in acc field (handle-error-fn tprops problem))
(contains? tprops :error/message)
(assoc acc field (handle-error-message tprops))
(assoc-in acc field (handle-error-message tprops))
(contains? tprops :error/code)
(assoc acc field (handle-error-code tprops))
(assoc-in acc field (handle-error-code tprops))
:else
(assoc acc field {:message (tr "errors.invalid-data")})))))
(assoc-in acc field {:message (tr "errors.invalid-data")})))))
(defn- use-rerender-fn
[]
@@ -114,20 +118,25 @@
[f {:keys [schema validators]}]
(fn [& args]
(let [state (apply f args)
cleaned (sm/decode schema (:data state) sm/string-transformer)
cleaned (sm/decode schema (:data state) sm/json-transformer)
valid? (sm/validate schema cleaned)
errors (when-not valid?
(collect-schema-errors schema validators state))]
errors
(when-not valid?
(collect-schema-errors schema validators state))
extra-errors
(not-empty (:extra-errors state))]
(assoc state
:errors errors
:clean-data (when valid? cleaned)
:valid (and (not errors) valid?)))))
:valid (and (not errors)
(not extra-errors)
valid?)))))
(defn- create-form-mutator
[internal-state rerender-fn wrap-update-fn initial opts]
(mf/set-ref-val! internal-state initial)
(reify
IDeref
(-deref [_]
@@ -162,7 +171,7 @@
(rerender-fn)))))
(defn use-form
[& {:keys [initial] :as opts}]
[& {:keys [initial schema validators] :as opts}]
(let [rerender-fn (use-rerender-fn)
initial
@@ -175,8 +184,15 @@
(mf/use-ref nil)
form-mutator
(mf/with-memo [initial]
(create-form-mutator internal-state rerender-fn wrap-update-schema-fn initial opts))]
(mf/with-memo [initial schema validators]
(let [mutator (create-form-mutator internal-state rerender-fn wrap-update-schema-fn
initial
(select-keys opts [:schema :validators]))]
(swap! mutator identity)
mutator))]
(mf/with-effect [initial]
(mf/set-ref-val! internal-state initial))
;; Initialize internal state once
(mf/with-layout-effect []
@@ -191,11 +207,16 @@
([form field value]
(on-input-change form field value false))
([form field value trim?]
(letfn [(clean-errors [errors]
(-> errors
(dissoc field)
(not-empty)))]
(swap! form (fn [state]
(-> state
(assoc-in [:touched field] true)
(assoc-in [:data field] (if trim? (str/trim value) value))
(update :errors dissoc field))))))
(update :errors clean-errors)
(update :extra-errors clean-errors)))))))
(defn update-input-value!
[form field value]