mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🎉 Replace font family form (#7825)
This commit is contained in:
@@ -303,7 +303,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.pressSequentially(".changed");
|
||||
|
||||
await tokensUpdateCreateModal.getByRole("button", {name: "Save"}).click();
|
||||
await tokensUpdateCreateModal.getByRole("button", { name: "Save" }).click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}])]))
|
||||
|
||||
|
||||
@@ -12,3 +12,7 @@
|
||||
gap: var(--sp-xs);
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.hint-formated {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
@@ -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?)
|
||||
|
||||
@@ -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"}]])]
|
||||
|
||||
@@ -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"}]]])
|
||||
|
||||
@@ -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"}]]]))
|
||||
|
||||
|
||||
@@ -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) ")")
|
||||
|
||||
@@ -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"}]]]
|
||||
|
||||
|
||||
@@ -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") "")
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
}
|
||||
|
||||
.constraints-selects {
|
||||
@include deprecated.flexColumn;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.horizontal-select,
|
||||
|
||||
@@ -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)}]]]))
|
||||
|
||||
|
||||
@@ -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"}]]]])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")]]]]))
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user