mirror of
https://github.com/penpot/penpot.git
synced 2026-09-07 04:29:31 +00:00
♻️ Refactor formcomponent structure
This commit is contained in:
parent
8e0aa683a1
commit
d5e4b6f57e
@ -11,7 +11,6 @@
|
|||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.tokens :as cft]
|
[app.common.files.tokens :as cft]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.types.color :as c]
|
|
||||||
[app.common.types.token :as cto]
|
[app.common.types.token :as cto]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.main.constants :refer [max-input-length]]
|
[app.main.constants :refer [max-input-length]]
|
||||||
@ -22,22 +21,19 @@
|
|||||||
[app.main.data.workspace.tokens.errors :as wte]
|
[app.main.data.workspace.tokens.errors :as wte]
|
||||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||||
[app.main.fonts :as fonts]
|
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[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.buttons.button :refer [button*]]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
|
||||||
[app.main.ui.ds.controls.input :refer [input*]]
|
[app.main.ui.ds.controls.input :refer [input*]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||||
[app.main.ui.icons :as deprecated-icon]
|
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token*]]
|
||||||
[app.main.ui.workspace.colorpicker :as colorpicker]
|
[app.main.ui.workspace.tokens.management.create.shadow :refer [shadow-value-inputs*]]
|
||||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
[app.main.ui.workspace.tokens.management.create.shared.color-picker :refer [color-picker*]]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
|
[app.main.ui.workspace.tokens.management.create.shared.composite-tabs :refer [composite-tabs*]]
|
||||||
[app.main.ui.workspace.tokens.management.create.input-token-color-bullet :refer [input-token-color-bullet*]]
|
[app.main.ui.workspace.tokens.management.create.shared.font-combobox :refer [font-picker-combobox*]]
|
||||||
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token* token-value-hint*]]
|
[app.main.ui.workspace.tokens.management.create.typography-composite :refer [typography-value-inputs*]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.functions :as uf]
|
[app.util.functions :as uf]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
@ -659,104 +655,6 @@
|
|||||||
:disabled disabled?}
|
:disabled disabled?}
|
||||||
(tr "labels.save")]]]]))
|
(tr "labels.save")]]]]))
|
||||||
|
|
||||||
;; Tabs Component --------------------------------------------------------------
|
|
||||||
|
|
||||||
(mf/defc composite-reference-input*
|
|
||||||
[{:keys [default-value on-blur on-update-value token-resolve-result reference-label reference-icon is-reference-fn]}]
|
|
||||||
[:> input-token*
|
|
||||||
{:aria-label (tr "labels.reference")
|
|
||||||
:placeholder reference-label
|
|
||||||
:icon reference-icon
|
|
||||||
:default-value (when (is-reference-fn default-value) default-value)
|
|
||||||
:on-blur on-blur
|
|
||||||
:on-change on-update-value
|
|
||||||
:token-resolve-result (when (or
|
|
||||||
(:errors token-resolve-result)
|
|
||||||
(string? (:value token-resolve-result)))
|
|
||||||
token-resolve-result)}])
|
|
||||||
|
|
||||||
(mf/defc composite-tabs*
|
|
||||||
[{:keys [default-value
|
|
||||||
on-update-value
|
|
||||||
on-external-update-value
|
|
||||||
on-value-resolve
|
|
||||||
clear-resolve-value
|
|
||||||
custom-input-token-value-props]
|
|
||||||
:rest props}]
|
|
||||||
(let [;; Active Tab State
|
|
||||||
{:keys [active-tab
|
|
||||||
composite-tab
|
|
||||||
is-reference-fn
|
|
||||||
reference-icon
|
|
||||||
reference-label
|
|
||||||
set-active-tab
|
|
||||||
title
|
|
||||||
update-composite-backup-value]} custom-input-token-value-props
|
|
||||||
reference-tab-active? (= :reference active-tab)
|
|
||||||
;; Backup value ref
|
|
||||||
;; Used to restore the previously entered value when switching tabs
|
|
||||||
;; Uses ref to not trigger state updates during update
|
|
||||||
backup-state-ref (mf/use-var
|
|
||||||
(if reference-tab-active?
|
|
||||||
{:reference default-value}
|
|
||||||
{:composite default-value}))
|
|
||||||
default-value (get @backup-state-ref active-tab)
|
|
||||||
|
|
||||||
on-toggle-tab
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps active-tab on-external-update-value on-value-resolve clear-resolve-value)
|
|
||||||
(fn []
|
|
||||||
(let [next-tab (if (= active-tab :composite) :reference :composite)]
|
|
||||||
;; Clear the resolved value so it wont show up before the next-tab value has resolved
|
|
||||||
(clear-resolve-value)
|
|
||||||
;; Restore the internal value from backup
|
|
||||||
(on-external-update-value (get @backup-state-ref next-tab))
|
|
||||||
(set-active-tab next-tab))))
|
|
||||||
|
|
||||||
update-composite-value
|
|
||||||
(mf/use-fn
|
|
||||||
(fn [f]
|
|
||||||
(clear-resolve-value)
|
|
||||||
(swap! backup-state-ref f)
|
|
||||||
(on-external-update-value (get @backup-state-ref :composite))))
|
|
||||||
|
|
||||||
;; Store updated value in backup-state-ref
|
|
||||||
on-update-value'
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-update-value reference-tab-active? update-composite-backup-value)
|
|
||||||
(fn [e]
|
|
||||||
(if reference-tab-active?
|
|
||||||
(swap! backup-state-ref assoc :reference (dom/get-target-val e))
|
|
||||||
(swap! backup-state-ref update :composite #(update-composite-backup-value % e)))
|
|
||||||
(on-update-value e)))]
|
|
||||||
[:div {:class (stl/css :typography-inputs-row)}
|
|
||||||
[:div {:class (stl/css :title-bar)}
|
|
||||||
[:div {:class (stl/css :title)} title]
|
|
||||||
[:& radio-buttons {:class (stl/css :listing-options)
|
|
||||||
:selected (if reference-tab-active? "reference" "composite")
|
|
||||||
:on-change on-toggle-tab
|
|
||||||
:name "reference-composite-tab"}
|
|
||||||
[:& radio-button {:icon deprecated-icon/layers
|
|
||||||
:value "composite"
|
|
||||||
:title (tr "workspace.tokens.individual-tokens")
|
|
||||||
:id "composite-opt"}]
|
|
||||||
[:& radio-button {:icon deprecated-icon/tokens
|
|
||||||
:value "reference"
|
|
||||||
:title (tr "workspace.tokens.use-reference")
|
|
||||||
:id "reference-opt"}]]]
|
|
||||||
[:div {:class (stl/css :typography-inputs)}
|
|
||||||
(if reference-tab-active?
|
|
||||||
[:> composite-reference-input*
|
|
||||||
(mf/spread-props props {:default-value default-value
|
|
||||||
:on-update-value on-update-value'
|
|
||||||
:reference-icon reference-icon
|
|
||||||
:reference-label reference-label
|
|
||||||
:is-reference-fn is-reference-fn})]
|
|
||||||
[:> composite-tab
|
|
||||||
(mf/spread-props props {:default-value default-value
|
|
||||||
:on-update-value on-update-value'
|
|
||||||
:update-composite-value update-composite-value})])]]))
|
|
||||||
|
|
||||||
(mf/defc composite-form*
|
(mf/defc composite-form*
|
||||||
"Wrapper around form* that manages composite/reference tab state.
|
"Wrapper around form* that manages composite/reference tab state.
|
||||||
Takes the same props as form* plus a function to determine if a token value is a reference."
|
Takes the same props as form* plus a function to determine if a token value is a reference."
|
||||||
@ -793,124 +691,6 @@
|
|||||||
|
|
||||||
;; Token Type Forms ------------------------------------------------------------
|
;; Token Type Forms ------------------------------------------------------------
|
||||||
|
|
||||||
;; FIXME: this function has confusing name
|
|
||||||
(defn- hex->value
|
|
||||||
[hex]
|
|
||||||
(when-let [tc (tinycolor/valid-color hex)]
|
|
||||||
(let [hex (tinycolor/->hex-string tc)
|
|
||||||
alpha (tinycolor/alpha tc)
|
|
||||||
[r g b] (c/hex->rgb hex)
|
|
||||||
[h s v] (c/hex->hsv hex)]
|
|
||||||
{:hex hex
|
|
||||||
:r r :g g :b b
|
|
||||||
:h h :s s :v v
|
|
||||||
:alpha alpha})))
|
|
||||||
|
|
||||||
(mf/defc ramp*
|
|
||||||
[{:keys [color on-change]}]
|
|
||||||
(let [wrapper-node-ref (mf/use-ref nil)
|
|
||||||
dragging-ref (mf/use-ref false)
|
|
||||||
|
|
||||||
on-start-drag
|
|
||||||
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
|
|
||||||
|
|
||||||
on-finish-drag
|
|
||||||
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
|
|
||||||
|
|
||||||
internal-color*
|
|
||||||
(mf/use-state #(hex->value color))
|
|
||||||
|
|
||||||
internal-color
|
|
||||||
(deref internal-color*)
|
|
||||||
|
|
||||||
on-change'
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-change)
|
|
||||||
(fn [{:keys [hex alpha] :as selector-color}]
|
|
||||||
(let [dragging? (mf/ref-val dragging-ref)]
|
|
||||||
(when-not (and dragging? hex)
|
|
||||||
(reset! internal-color* selector-color)
|
|
||||||
(on-change hex alpha)))))]
|
|
||||||
(mf/use-effect
|
|
||||||
(mf/deps color)
|
|
||||||
(fn []
|
|
||||||
;; Update internal color when user changes input value
|
|
||||||
(when-let [color (tinycolor/valid-color color)]
|
|
||||||
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
|
|
||||||
(reset! internal-color* (hex->value color))))))
|
|
||||||
|
|
||||||
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
|
|
||||||
[:div {:ref wrapper-node-ref}
|
|
||||||
[:> ramp-selector*
|
|
||||||
{:color internal-color
|
|
||||||
:on-start-drag on-start-drag
|
|
||||||
:on-finish-drag on-finish-drag
|
|
||||||
:on-change on-change'}]]))
|
|
||||||
|
|
||||||
(mf/defc color-picker*
|
|
||||||
[{:keys [placeholder label default-value input-ref on-blur on-update-value on-external-update-value custom-input-token-value-props token-resolve-result]}]
|
|
||||||
(let [{:keys [color on-display-colorpicker]} custom-input-token-value-props
|
|
||||||
color-ramp-open* (mf/use-state false)
|
|
||||||
color-ramp-open? (deref color-ramp-open*)
|
|
||||||
|
|
||||||
on-click-swatch
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps color-ramp-open? on-display-colorpicker)
|
|
||||||
(fn []
|
|
||||||
(let [open? (not color-ramp-open?)]
|
|
||||||
(reset! color-ramp-open* open?)
|
|
||||||
(when on-display-colorpicker
|
|
||||||
(on-display-colorpicker open?)))))
|
|
||||||
|
|
||||||
swatch
|
|
||||||
(mf/html
|
|
||||||
[:> input-token-color-bullet*
|
|
||||||
{:color color
|
|
||||||
:class (stl/css :slot-start)
|
|
||||||
:on-click on-click-swatch}])
|
|
||||||
|
|
||||||
on-change'
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps color on-external-update-value)
|
|
||||||
(fn [hex-value alpha]
|
|
||||||
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
|
|
||||||
prev-input-color (some-> (dom/get-value (mf/ref-val input-ref))
|
|
||||||
(tinycolor/valid-color))
|
|
||||||
;; If the input is a reference we will take the format from the computed value
|
|
||||||
prev-computed-color (when-not prev-input-color
|
|
||||||
(some-> color (tinycolor/valid-color)))
|
|
||||||
prev-format (some-> (or prev-input-color prev-computed-color)
|
|
||||||
(tinycolor/color-format))
|
|
||||||
to-rgba? (and
|
|
||||||
(< alpha 1)
|
|
||||||
(or (= prev-format "hex") (not prev-format)))
|
|
||||||
to-hex? (and (not prev-format) (= alpha 1))
|
|
||||||
format (cond
|
|
||||||
to-rgba? "rgba"
|
|
||||||
to-hex? "hex"
|
|
||||||
prev-format prev-format
|
|
||||||
:else "hex")
|
|
||||||
color-value (-> (tinycolor/valid-color hex-value)
|
|
||||||
(tinycolor/set-alpha (or alpha 1))
|
|
||||||
(tinycolor/->string format))]
|
|
||||||
(dom/set-value! (mf/ref-val input-ref) color-value)
|
|
||||||
(on-external-update-value color-value))))]
|
|
||||||
|
|
||||||
[:*
|
|
||||||
[:> input-token*
|
|
||||||
{:placeholder placeholder
|
|
||||||
:label label
|
|
||||||
:default-value default-value
|
|
||||||
:ref input-ref
|
|
||||||
:on-blur on-blur
|
|
||||||
:on-change on-update-value
|
|
||||||
:slot-start swatch}]
|
|
||||||
(when color-ramp-open?
|
|
||||||
[:> ramp*
|
|
||||||
{:color (some-> color (tinycolor/valid-color))
|
|
||||||
:on-change on-change'}])
|
|
||||||
[:> token-value-hint* {:result token-resolve-result}]]))
|
|
||||||
|
|
||||||
(mf/defc color-form*
|
(mf/defc color-form*
|
||||||
[{:keys [token on-display-colorpicker] :rest props}]
|
[{:keys [token on-display-colorpicker] :rest props}]
|
||||||
(let [color* (mf/use-state (:value token))
|
(let [color* (mf/use-state (:value token))
|
||||||
@ -945,238 +725,6 @@
|
|||||||
:custom-input-token-value color-picker*
|
:custom-input-token-value color-picker*
|
||||||
:custom-input-token-value-props custom-input-token-value-props})]))
|
:custom-input-token-value-props custom-input-token-value-props})]))
|
||||||
|
|
||||||
(mf/defc shadow-color-picker-wrapper*
|
|
||||||
"Wrapper for color-picker* that passes shadow color state from parent.
|
|
||||||
Similar to color-form* but receives color state from shadow-value-inputs*."
|
|
||||||
[{:keys [placeholder label default-value input-ref on-update-value on-external-update-value token-resolve-result shadow-color]}]
|
|
||||||
(let [;; Use the color state passed from parent (shadow-value-inputs*)
|
|
||||||
resolved-color (get token-resolve-result :resolved-value)
|
|
||||||
color (or shadow-color resolved-color default-value "")
|
|
||||||
|
|
||||||
custom-input-token-value-props
|
|
||||||
(mf/use-memo
|
|
||||||
(mf/deps color)
|
|
||||||
(fn []
|
|
||||||
{:color color}))]
|
|
||||||
|
|
||||||
[:> color-picker*
|
|
||||||
{:placeholder placeholder
|
|
||||||
:label label
|
|
||||||
:default-value default-value
|
|
||||||
:input-ref input-ref
|
|
||||||
:on-update-value on-update-value
|
|
||||||
:on-external-update-value on-external-update-value
|
|
||||||
:custom-input-token-value-props custom-input-token-value-props
|
|
||||||
:token-resolve-result token-resolve-result}]))
|
|
||||||
|
|
||||||
(def ^:private shadow-inputs
|
|
||||||
#(d/ordered-map
|
|
||||||
:offsetX
|
|
||||||
{:label (tr "workspace.tokens.shadow-x")
|
|
||||||
:placeholder (tr "workspace.tokens.shadow-x")}
|
|
||||||
:offsetY
|
|
||||||
{:label (tr "workspace.tokens.shadow-y")
|
|
||||||
:placeholder (tr "workspace.tokens.shadow-y")}
|
|
||||||
:blur
|
|
||||||
{:label (tr "workspace.tokens.shadow-blur")
|
|
||||||
:placeholder (tr "workspace.tokens.shadow-blur")}
|
|
||||||
:spread
|
|
||||||
{:label (tr "workspace.tokens.shadow-spread")
|
|
||||||
:placeholder (tr "workspace.tokens.shadow-spread")}
|
|
||||||
:color
|
|
||||||
{:label (tr "workspace.tokens.shadow-color")
|
|
||||||
:placeholder (tr "workspace.tokens.shadow-color")}
|
|
||||||
:inset
|
|
||||||
{:label (tr "workspace.tokens.shadow-inset")
|
|
||||||
:placeholder (tr "workspace.tokens.shadow-inset")}))
|
|
||||||
|
|
||||||
(mf/defc inset-type-select*
|
|
||||||
[{:keys [default-value shadow-idx label on-change]}]
|
|
||||||
(let [selected* (mf/use-state (or (str default-value) "false"))
|
|
||||||
selected (deref selected*)
|
|
||||||
|
|
||||||
on-change
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-change selected shadow-idx)
|
|
||||||
(fn [value e]
|
|
||||||
(obj/set! e "tokenValue" (if (= "true" value) true false))
|
|
||||||
(on-change e)
|
|
||||||
(reset! selected* (str value))))]
|
|
||||||
[:div {:class (stl/css :input-row)}
|
|
||||||
[:div {:class (stl/css :inset-label)} label]
|
|
||||||
[:& radio-buttons {:selected selected
|
|
||||||
:on-change on-change
|
|
||||||
:name (str "inset-select-" shadow-idx)}
|
|
||||||
[:& radio-button {:value "false"
|
|
||||||
:title "false"
|
|
||||||
:icon "❌"
|
|
||||||
:id (str "inset-default-" shadow-idx)}]
|
|
||||||
[:& radio-button {:value "true"
|
|
||||||
:title "true"
|
|
||||||
:icon "✅"
|
|
||||||
:id (str "inset-false-" shadow-idx)}]]]))
|
|
||||||
|
|
||||||
(mf/defc shadow-input*
|
|
||||||
[{:keys [default-value label placeholder shadow-idx input-type on-update-value on-external-update-value token-resolve-result errors-by-key shadow-color]}]
|
|
||||||
(let [color-input-ref (mf/use-ref)
|
|
||||||
|
|
||||||
on-change
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps shadow-idx input-type on-update-value)
|
|
||||||
(fn [e]
|
|
||||||
(-> (obj/set! e "tokenTypeAtIndex" [shadow-idx input-type])
|
|
||||||
(on-update-value))))
|
|
||||||
|
|
||||||
on-external-update-value'
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps shadow-idx input-type on-external-update-value)
|
|
||||||
(fn [v]
|
|
||||||
(on-external-update-value [shadow-idx input-type] v)))
|
|
||||||
|
|
||||||
resolved (get-in token-resolve-result [:resolved-value shadow-idx input-type])
|
|
||||||
|
|
||||||
errors (get errors-by-key input-type)
|
|
||||||
|
|
||||||
should-show? (or (some? resolved) (seq errors))
|
|
||||||
|
|
||||||
token-prop (when should-show?
|
|
||||||
(d/without-nils
|
|
||||||
{:resolved-value resolved
|
|
||||||
:errors errors}))]
|
|
||||||
(case input-type
|
|
||||||
:inset
|
|
||||||
[:> inset-type-select*
|
|
||||||
{:default-value default-value
|
|
||||||
:shadow-idx shadow-idx
|
|
||||||
:label label
|
|
||||||
:on-change on-change}]
|
|
||||||
:color
|
|
||||||
[:> shadow-color-picker-wrapper*
|
|
||||||
{:placeholder placeholder
|
|
||||||
:label label
|
|
||||||
:default-value default-value
|
|
||||||
:input-ref color-input-ref
|
|
||||||
:on-update-value on-change
|
|
||||||
:on-external-update-value on-external-update-value'
|
|
||||||
:token-resolve-result token-prop
|
|
||||||
:shadow-color shadow-color
|
|
||||||
:data-testid (str "shadow-color-input-" shadow-idx)}]
|
|
||||||
[:div {:class (stl/css :input-row)
|
|
||||||
:data-testid (str "shadow-" (name input-type) "-input-" shadow-idx)}
|
|
||||||
[:> input-token*
|
|
||||||
{:label label
|
|
||||||
:placeholder placeholder
|
|
||||||
:default-value default-value
|
|
||||||
:on-change on-change
|
|
||||||
:token-resolve-result token-prop}]])))
|
|
||||||
|
|
||||||
(mf/defc shadow-input-fields*
|
|
||||||
[{:keys [shadow shadow-idx on-remove-shadow on-add-shadow is-remove-disabled on-update-value token-resolve-result errors-by-key on-external-update-value shadow-color] :as props}]
|
|
||||||
(let [on-remove-shadow
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps shadow-idx on-remove-shadow)
|
|
||||||
#(on-remove-shadow shadow-idx))]
|
|
||||||
[:div {:data-testid (str "shadow-input-fields-" shadow-idx)}
|
|
||||||
[:> icon-button* {:icon i/add
|
|
||||||
:type "button"
|
|
||||||
:on-click on-add-shadow
|
|
||||||
:data-testid (str "shadow-add-button-" shadow-idx)
|
|
||||||
:aria-label (tr "workspace.tokens.shadow-add-shadow")}]
|
|
||||||
[:> icon-button* {:variant "ghost"
|
|
||||||
:type "button"
|
|
||||||
:icon i/remove
|
|
||||||
:on-click on-remove-shadow
|
|
||||||
:disabled is-remove-disabled
|
|
||||||
:data-testid (str "shadow-remove-button-" shadow-idx)
|
|
||||||
:aria-label (tr "workspace.tokens.shadow-remove-shadow")}]
|
|
||||||
(for [[input-type {:keys [label placeholder]}] (shadow-inputs)]
|
|
||||||
[:> shadow-input*
|
|
||||||
{:key (str input-type shadow-idx)
|
|
||||||
:input-type input-type
|
|
||||||
:label label
|
|
||||||
:placeholder placeholder
|
|
||||||
:shadow-idx shadow-idx
|
|
||||||
:default-value (get shadow input-type)
|
|
||||||
:on-update-value on-update-value
|
|
||||||
:token-resolve-result token-resolve-result
|
|
||||||
:errors-by-key errors-by-key
|
|
||||||
:on-external-update-value on-external-update-value
|
|
||||||
:shadow-color shadow-color}])]))
|
|
||||||
|
|
||||||
(mf/defc shadow-value-inputs*
|
|
||||||
[{:keys [default-value on-update-value token-resolve-result update-composite-value] :as props}]
|
|
||||||
(let [shadows* (mf/use-state (or default-value [{}]))
|
|
||||||
shadows (deref shadows*)
|
|
||||||
shadows-count (count shadows)
|
|
||||||
composite-token? (not (cto/typography-composite-token-reference? (:value token-resolve-result)))
|
|
||||||
|
|
||||||
;; Maintain a map of color states for each shadow to prevent reset on add/remove
|
|
||||||
shadow-colors* (mf/use-state {})
|
|
||||||
shadow-colors (deref shadow-colors*)
|
|
||||||
|
|
||||||
;; Initialize color states for each shadow index
|
|
||||||
_ (mf/use-effect
|
|
||||||
(mf/deps shadows)
|
|
||||||
(fn []
|
|
||||||
(doseq [[idx shadow] (d/enumerate shadows)]
|
|
||||||
(when-not (contains? shadow-colors idx)
|
|
||||||
(let [resolved-color (get-in token-resolve-result [:resolved-value idx :color])
|
|
||||||
initial-color (or resolved-color (get shadow :color) "")]
|
|
||||||
(swap! shadow-colors* assoc idx initial-color))))))
|
|
||||||
|
|
||||||
;; Define on-external-update-value here where we have access to on-update-value
|
|
||||||
on-external-update-value
|
|
||||||
(mf/use-callback
|
|
||||||
(mf/deps on-update-value shadow-colors*)
|
|
||||||
(fn [token-type-at-index value]
|
|
||||||
(let [[idx token-type] token-type-at-index
|
|
||||||
e (js-obj)]
|
|
||||||
;; Update shadow color state if this is a color update
|
|
||||||
(when (= token-type :color)
|
|
||||||
(swap! shadow-colors* assoc idx value))
|
|
||||||
(obj/set! e "tokenTypeAtIndex" token-type-at-index)
|
|
||||||
(obj/set! e "target" #js {:value value})
|
|
||||||
(on-update-value e))))
|
|
||||||
|
|
||||||
on-add-shadow
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps shadows update-composite-value)
|
|
||||||
(fn []
|
|
||||||
(update-composite-value
|
|
||||||
(fn [state]
|
|
||||||
(let [new-state (update state :composite (fnil conj []) {})]
|
|
||||||
(reset! shadows* (:composite new-state))
|
|
||||||
new-state)))))
|
|
||||||
|
|
||||||
on-remove-shadow
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps shadows update-composite-value)
|
|
||||||
(fn [idx]
|
|
||||||
(update-composite-value
|
|
||||||
(fn [state]
|
|
||||||
(let [new-state (update state :composite d/remove-at-index idx)]
|
|
||||||
(reset! shadows* (:composite new-state))
|
|
||||||
new-state)))))]
|
|
||||||
[:div {:class (stl/css :nested-input-row)}
|
|
||||||
(for [[shadow-idx shadow] (d/enumerate shadows)
|
|
||||||
:let [is-remove-disabled (= shadows-count 1)
|
|
||||||
key (str shadows-count shadow-idx)
|
|
||||||
errors-by-key (when composite-token?
|
|
||||||
(sd/collect-shadow-errors token-resolve-result shadow-idx))]]
|
|
||||||
[:div {:key key
|
|
||||||
:class (stl/css :nested-input-row)}
|
|
||||||
[:> shadow-input-fields*
|
|
||||||
{:is-remove-disabled is-remove-disabled
|
|
||||||
:shadow-idx shadow-idx
|
|
||||||
:on-add-shadow on-add-shadow
|
|
||||||
:on-remove-shadow on-remove-shadow
|
|
||||||
:shadow shadow
|
|
||||||
:on-update-value on-update-value
|
|
||||||
:token-resolve-result token-resolve-result
|
|
||||||
:errors-by-key errors-by-key
|
|
||||||
:on-external-update-value on-external-update-value
|
|
||||||
:shadow-color (get shadow-colors shadow-idx "")}]])]))
|
|
||||||
|
|
||||||
(mf/defc shadow-form*
|
(mf/defc shadow-form*
|
||||||
[{:keys [token] :rest props}]
|
[{:keys [token] :rest props}]
|
||||||
(let [on-get-token-value
|
(let [on-get-token-value
|
||||||
@ -1217,85 +765,6 @@
|
|||||||
:on-get-token-value on-get-token-value
|
:on-get-token-value on-get-token-value
|
||||||
:update-composite-backup-value update-composite-backup-value})]))
|
:update-composite-backup-value update-composite-backup-value})]))
|
||||||
|
|
||||||
(mf/defc font-selector-wrapper*
|
|
||||||
[{:keys [font input-ref on-select-font on-close-font-selector]}]
|
|
||||||
(let [current-font* (mf/use-state (or font
|
|
||||||
(some-> (mf/ref-val input-ref)
|
|
||||||
(dom/get-value)
|
|
||||||
(cto/split-font-family)
|
|
||||||
(first)
|
|
||||||
(fonts/find-font-family))))
|
|
||||||
current-font (deref current-font*)]
|
|
||||||
[:div {:class (stl/css :font-select-wrapper)}
|
|
||||||
[:> font-selector* {:current-font current-font
|
|
||||||
:on-select on-select-font
|
|
||||||
:on-close on-close-font-selector
|
|
||||||
:full-size true}]]))
|
|
||||||
|
|
||||||
(mf/defc font-picker-combobox*
|
|
||||||
[{:keys [default-value label aria-label input-ref on-blur on-update-value on-external-update-value token-resolve-result placeholder]}]
|
|
||||||
(let [font* (mf/use-state (fonts/find-font-family default-value))
|
|
||||||
font (deref font*)
|
|
||||||
set-font (mf/use-fn
|
|
||||||
(mf/deps font)
|
|
||||||
#(reset! font* %))
|
|
||||||
|
|
||||||
font-selector-open* (mf/use-state false)
|
|
||||||
font-selector-open? (deref font-selector-open*)
|
|
||||||
|
|
||||||
on-close-font-selector
|
|
||||||
(mf/use-fn
|
|
||||||
(fn []
|
|
||||||
(reset! font-selector-open* false)))
|
|
||||||
|
|
||||||
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?))))
|
|
||||||
|
|
||||||
on-select-font
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-external-update-value set-font font)
|
|
||||||
(fn [{:keys [family] :as font}]
|
|
||||||
(when font
|
|
||||||
(set-font font)
|
|
||||||
(on-external-update-value family))))
|
|
||||||
|
|
||||||
on-update-value'
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-update-value set-font)
|
|
||||||
(fn [value]
|
|
||||||
(set-font nil)
|
|
||||||
(on-update-value value)))
|
|
||||||
|
|
||||||
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"}])]
|
|
||||||
[:*
|
|
||||||
[:> input-token*
|
|
||||||
{:placeholder (or placeholder (tr "workspace.tokens.token-font-family-value-enter"))
|
|
||||||
:label label
|
|
||||||
:aria-label aria-label
|
|
||||||
:default-value (or (:name font) default-value)
|
|
||||||
:ref input-ref
|
|
||||||
:on-blur on-blur
|
|
||||||
:on-change on-update-value'
|
|
||||||
:icon i/text-font-family
|
|
||||||
:slot-end font-selector-button
|
|
||||||
:token-resolve-result token-resolve-result}]
|
|
||||||
(when font-selector-open?
|
|
||||||
[:> font-selector-wrapper* {:font font
|
|
||||||
:input-ref input-ref
|
|
||||||
:on-select-font on-select-font
|
|
||||||
:on-close-font-selector on-close-font-selector}])]))
|
|
||||||
|
|
||||||
(mf/defc font-family-form*
|
(mf/defc font-family-form*
|
||||||
[{:keys [token] :rest props}]
|
[{:keys [token] :rest props}]
|
||||||
(let [on-value-resolve
|
(let [on-value-resolve
|
||||||
@ -1327,98 +796,6 @@
|
|||||||
(mf/spread-props props {:token token
|
(mf/spread-props props {:token token
|
||||||
:input-value-placeholder (tr "workspace.tokens.font-weight-value-enter")})])
|
:input-value-placeholder (tr "workspace.tokens.font-weight-value-enter")})])
|
||||||
|
|
||||||
(def ^:private typography-inputs
|
|
||||||
#(d/ordered-map
|
|
||||||
:font-family
|
|
||||||
{:label (tr "workspace.tokens.token-font-family-value")
|
|
||||||
:icon i/text-font-family
|
|
||||||
:placeholder (tr "workspace.tokens.token-font-family-value-enter")}
|
|
||||||
:font-size
|
|
||||||
{:label "Font Size"
|
|
||||||
:icon i/text-font-size
|
|
||||||
:placeholder (tr "workspace.tokens.font-size-value-enter")}
|
|
||||||
:font-weight
|
|
||||||
{:label "Font Weight"
|
|
||||||
:icon i/text-font-weight
|
|
||||||
:placeholder (tr "workspace.tokens.font-weight-value-enter")}
|
|
||||||
:line-height
|
|
||||||
{:label "Line Height"
|
|
||||||
:icon i/text-lineheight
|
|
||||||
:placeholder (tr "workspace.tokens.line-height-value-enter")}
|
|
||||||
:letter-spacing
|
|
||||||
{:label "Letter Spacing"
|
|
||||||
:icon i/text-letterspacing
|
|
||||||
:placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")}
|
|
||||||
:text-case
|
|
||||||
{:label "Text Case"
|
|
||||||
:icon i/text-mixed
|
|
||||||
:placeholder (tr "workspace.tokens.text-case-value-enter")}
|
|
||||||
:text-decoration
|
|
||||||
{:label "Text Decoration"
|
|
||||||
:icon i/text-underlined
|
|
||||||
:placeholder (tr "workspace.tokens.text-decoration-value-enter")}))
|
|
||||||
|
|
||||||
(mf/defc typography-value-inputs*
|
|
||||||
[{:keys [default-value on-blur on-update-value token-resolve-result]}]
|
|
||||||
(let [composite-token? (not (cto/typography-composite-token-reference? (:value token-resolve-result)))
|
|
||||||
typography-inputs (mf/use-memo typography-inputs)
|
|
||||||
errors-by-key (sd/collect-typography-errors token-resolve-result)]
|
|
||||||
[:div {:class (stl/css :nested-input-row)}
|
|
||||||
(for [[token-type {:keys [label placeholder icon]}] typography-inputs]
|
|
||||||
(let [value (get default-value token-type)
|
|
||||||
resolved (get-in token-resolve-result [:resolved-value token-type])
|
|
||||||
errors (get errors-by-key token-type)
|
|
||||||
|
|
||||||
should-show? (or (and (some? resolved)
|
|
||||||
(not= value (str resolved)))
|
|
||||||
(seq errors))
|
|
||||||
|
|
||||||
token-prop (when (and composite-token? should-show?)
|
|
||||||
(d/without-nils
|
|
||||||
{:resolved-value (when-not (str/empty? resolved) resolved)
|
|
||||||
:errors errors}))
|
|
||||||
|
|
||||||
input-ref (mf/use-ref)
|
|
||||||
|
|
||||||
on-external-update-value
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps on-update-value)
|
|
||||||
(fn [next-value]
|
|
||||||
(let [element (mf/ref-val input-ref)]
|
|
||||||
(dom/set-value! element next-value)
|
|
||||||
(on-update-value #js {:target element
|
|
||||||
:tokenType :font-family}))))
|
|
||||||
|
|
||||||
on-change
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps token-type)
|
|
||||||
;; Passing token-type via event to prevent deep function adapting & passing of type
|
|
||||||
(fn [event]
|
|
||||||
(-> (obj/set! event "tokenType" token-type)
|
|
||||||
(on-update-value))))]
|
|
||||||
|
|
||||||
[:div {:key (str token-type)
|
|
||||||
:class (stl/css :input-row)}
|
|
||||||
(case token-type
|
|
||||||
:font-family
|
|
||||||
[:> font-picker-combobox*
|
|
||||||
{:aria-label label
|
|
||||||
:placeholder placeholder
|
|
||||||
:input-ref input-ref
|
|
||||||
:default-value (when value (cto/join-font-family value))
|
|
||||||
:on-blur on-blur
|
|
||||||
:on-update-value on-change
|
|
||||||
:on-external-update-value on-external-update-value
|
|
||||||
:token-resolve-result token-prop}]
|
|
||||||
[:> input-token*
|
|
||||||
{:aria-label label
|
|
||||||
:placeholder placeholder
|
|
||||||
:default-value value
|
|
||||||
:on-blur on-blur
|
|
||||||
:icon icon
|
|
||||||
:on-change on-change
|
|
||||||
:token-resolve-result token-prop}])]))]))
|
|
||||||
|
|
||||||
(mf/defc typography-form*
|
(mf/defc typography-form*
|
||||||
[{:keys [token] :rest props}]
|
[{:keys [token] :rest props}]
|
||||||
(let [on-get-token-value
|
(let [on-get-token-value
|
||||||
|
|||||||
@ -41,34 +41,6 @@
|
|||||||
gap: var(--sp-xs);
|
gap: var(--sp-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nested-input-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--sp-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.typography-inputs-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--sp-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.typography-inputs {
|
|
||||||
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
|
||||||
padding-inline-start: var(--sp-m);
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-name-change-notification-wrapper {
|
.warning-name-change-notification-wrapper {
|
||||||
margin-block-start: var(--sp-l);
|
margin-block-start: var(--sp-l);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,259 @@
|
|||||||
|
;; 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.shadow
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
|
||||||
|
[app.common.types.token :as cto]
|
||||||
|
[app.main.data.style-dictionary :as sd]
|
||||||
|
|
||||||
|
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||||
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
|
|
||||||
|
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token*]]
|
||||||
|
[app.main.ui.workspace.tokens.management.create.shared.color-picker :refer [color-picker*]]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
|
[app.util.object :as obj]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(mf/defc inset-type-select*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [default-value shadow-idx label on-change]}]
|
||||||
|
(let [selected* (mf/use-state (or (str default-value) "false"))
|
||||||
|
selected (deref selected*)
|
||||||
|
|
||||||
|
on-change
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-change selected shadow-idx)
|
||||||
|
(fn [value e]
|
||||||
|
(obj/set! e "tokenValue" (if (= "true" value) true false))
|
||||||
|
(on-change e)
|
||||||
|
(reset! selected* (str value))))]
|
||||||
|
[:div {:class (stl/css :input-row)}
|
||||||
|
[:div {:class (stl/css :inset-label)} label]
|
||||||
|
[:& radio-buttons {:selected selected
|
||||||
|
:on-change on-change
|
||||||
|
:name (str "inset-select-" shadow-idx)}
|
||||||
|
[:& radio-button {:value "false"
|
||||||
|
:title "false"
|
||||||
|
:icon "❌"
|
||||||
|
:id (str "inset-default-" shadow-idx)}]
|
||||||
|
[:& radio-button {:value "true"
|
||||||
|
:title "true"
|
||||||
|
:icon "✅"
|
||||||
|
:id (str "inset-false-" shadow-idx)}]]]))
|
||||||
|
|
||||||
|
(def ^:private shadow-inputs
|
||||||
|
#(d/ordered-map
|
||||||
|
:offsetX
|
||||||
|
{:label (tr "workspace.tokens.shadow-x")
|
||||||
|
:placeholder (tr "workspace.tokens.shadow-x")}
|
||||||
|
:offsetY
|
||||||
|
{:label (tr "workspace.tokens.shadow-y")
|
||||||
|
:placeholder (tr "workspace.tokens.shadow-y")}
|
||||||
|
:blur
|
||||||
|
{:label (tr "workspace.tokens.shadow-blur")
|
||||||
|
:placeholder (tr "workspace.tokens.shadow-blur")}
|
||||||
|
:spread
|
||||||
|
{:label (tr "workspace.tokens.shadow-spread")
|
||||||
|
:placeholder (tr "workspace.tokens.shadow-spread")}
|
||||||
|
:color
|
||||||
|
{:label (tr "workspace.tokens.shadow-color")
|
||||||
|
:placeholder (tr "workspace.tokens.shadow-color")}
|
||||||
|
:inset
|
||||||
|
{:label (tr "workspace.tokens.shadow-inset")
|
||||||
|
:placeholder (tr "workspace.tokens.shadow-inset")}))
|
||||||
|
|
||||||
|
(mf/defc shadow-color-picker-wrapper*
|
||||||
|
"Wrapper for color-picker* that passes shadow color state from parent.
|
||||||
|
Similar to color-form* but receives color state from shadow-value-inputs*."
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [placeholder label default-value input-ref on-update-value on-external-update-value token-resolve-result shadow-color]}]
|
||||||
|
(let [;; Use the color state passed from parent (shadow-value-inputs*)
|
||||||
|
resolved-color (get token-resolve-result :resolved-value)
|
||||||
|
color (or shadow-color resolved-color default-value "")
|
||||||
|
|
||||||
|
custom-input-token-value-props
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps color)
|
||||||
|
(fn []
|
||||||
|
{:color color}))]
|
||||||
|
|
||||||
|
[:> color-picker*
|
||||||
|
{:placeholder placeholder
|
||||||
|
:label label
|
||||||
|
:default-value default-value
|
||||||
|
:input-ref input-ref
|
||||||
|
:on-update-value on-update-value
|
||||||
|
:on-external-update-value on-external-update-value
|
||||||
|
:custom-input-token-value-props custom-input-token-value-props
|
||||||
|
:token-resolve-result token-resolve-result}]))
|
||||||
|
|
||||||
|
(mf/defc shadow-input*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [default-value label placeholder shadow-idx input-type on-update-value on-external-update-value token-resolve-result errors-by-key shadow-color]}]
|
||||||
|
(let [color-input-ref (mf/use-ref)
|
||||||
|
|
||||||
|
on-change
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps shadow-idx input-type on-update-value)
|
||||||
|
(fn [e]
|
||||||
|
(-> (obj/set! e "tokenTypeAtIndex" [shadow-idx input-type])
|
||||||
|
(on-update-value))))
|
||||||
|
|
||||||
|
on-external-update-value'
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps shadow-idx input-type on-external-update-value)
|
||||||
|
(fn [v]
|
||||||
|
(on-external-update-value [shadow-idx input-type] v)))
|
||||||
|
|
||||||
|
resolved (get-in token-resolve-result [:resolved-value shadow-idx input-type])
|
||||||
|
|
||||||
|
errors (get errors-by-key input-type)
|
||||||
|
|
||||||
|
should-show? (or (some? resolved) (seq errors))
|
||||||
|
|
||||||
|
token-prop (when should-show?
|
||||||
|
(d/without-nils
|
||||||
|
{:resolved-value resolved
|
||||||
|
:errors errors}))]
|
||||||
|
(case input-type
|
||||||
|
:inset
|
||||||
|
[:> inset-type-select*
|
||||||
|
{:default-value default-value
|
||||||
|
:shadow-idx shadow-idx
|
||||||
|
:label label
|
||||||
|
:on-change on-change}]
|
||||||
|
:color
|
||||||
|
[:> shadow-color-picker-wrapper*
|
||||||
|
{:placeholder placeholder
|
||||||
|
:label label
|
||||||
|
:default-value default-value
|
||||||
|
:input-ref color-input-ref
|
||||||
|
:on-update-value on-change
|
||||||
|
:on-external-update-value on-external-update-value'
|
||||||
|
:token-resolve-result token-prop
|
||||||
|
:shadow-color shadow-color
|
||||||
|
:data-testid (str "shadow-color-input-" shadow-idx)}]
|
||||||
|
[:div {:class (stl/css :input-row)
|
||||||
|
:data-testid (str "shadow-" (name input-type) "-input-" shadow-idx)}
|
||||||
|
[:> input-token*
|
||||||
|
{:label label
|
||||||
|
:placeholder placeholder
|
||||||
|
:default-value default-value
|
||||||
|
:on-change on-change
|
||||||
|
:token-resolve-result token-prop}]])))
|
||||||
|
|
||||||
|
(mf/defc shadow-input-fields*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [shadow shadow-idx on-remove-shadow on-add-shadow is-remove-disabled on-update-value token-resolve-result errors-by-key on-external-update-value shadow-color] :as props}]
|
||||||
|
(let [on-remove-shadow
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps shadow-idx on-remove-shadow)
|
||||||
|
#(on-remove-shadow shadow-idx))]
|
||||||
|
[:div {:data-testid (str "shadow-input-fields-" shadow-idx)}
|
||||||
|
[:> icon-button* {:icon i/add
|
||||||
|
:type "button"
|
||||||
|
:on-click on-add-shadow
|
||||||
|
:data-testid (str "shadow-add-button-" shadow-idx)
|
||||||
|
:aria-label (tr "workspace.tokens.shadow-add-shadow")}]
|
||||||
|
[:> icon-button* {:variant "ghost"
|
||||||
|
:type "button"
|
||||||
|
:icon i/remove
|
||||||
|
:on-click on-remove-shadow
|
||||||
|
:disabled is-remove-disabled
|
||||||
|
:data-testid (str "shadow-remove-button-" shadow-idx)
|
||||||
|
:aria-label (tr "workspace.tokens.shadow-remove-shadow")}]
|
||||||
|
(for [[input-type {:keys [label placeholder]}] (shadow-inputs)]
|
||||||
|
[:> shadow-input*
|
||||||
|
{:key (str input-type shadow-idx)
|
||||||
|
:input-type input-type
|
||||||
|
:label label
|
||||||
|
:placeholder placeholder
|
||||||
|
:shadow-idx shadow-idx
|
||||||
|
:default-value (get shadow input-type)
|
||||||
|
:on-update-value on-update-value
|
||||||
|
:token-resolve-result token-resolve-result
|
||||||
|
:errors-by-key errors-by-key
|
||||||
|
:on-external-update-value on-external-update-value
|
||||||
|
:shadow-color shadow-color}])]))
|
||||||
|
|
||||||
|
(mf/defc shadow-value-inputs*
|
||||||
|
[{:keys [default-value on-update-value token-resolve-result update-composite-value] :as props}]
|
||||||
|
(let [shadows* (mf/use-state (or default-value [{}]))
|
||||||
|
shadows (deref shadows*)
|
||||||
|
shadows-count (count shadows)
|
||||||
|
composite-token? (not (cto/typography-composite-token-reference? (:value token-resolve-result)))
|
||||||
|
|
||||||
|
;; Maintain a map of color states for each shadow to prevent reset on add/remove
|
||||||
|
shadow-colors* (mf/use-state {})
|
||||||
|
shadow-colors (deref shadow-colors*)
|
||||||
|
|
||||||
|
;; Define on-external-update-value here where we have access to on-update-value
|
||||||
|
on-external-update-value
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-update-value shadow-colors*)
|
||||||
|
(fn [token-type-at-index value]
|
||||||
|
(let [[idx token-type] token-type-at-index
|
||||||
|
e (js-obj)]
|
||||||
|
;; Update shadow color state if this is a color update
|
||||||
|
(when (= token-type :color)
|
||||||
|
(swap! shadow-colors* assoc idx value))
|
||||||
|
(obj/set! e "tokenTypeAtIndex" token-type-at-index)
|
||||||
|
(obj/set! e "target" #js {:value value})
|
||||||
|
(on-update-value e))))
|
||||||
|
|
||||||
|
on-add-shadow
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps shadows update-composite-value)
|
||||||
|
(fn []
|
||||||
|
(update-composite-value
|
||||||
|
(fn [state]
|
||||||
|
(let [new-state (update state :composite (fnil conj []) {})]
|
||||||
|
(reset! shadows* (:composite new-state))
|
||||||
|
new-state)))))
|
||||||
|
|
||||||
|
on-remove-shadow
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps shadows update-composite-value)
|
||||||
|
(fn [idx]
|
||||||
|
(update-composite-value
|
||||||
|
(fn [state]
|
||||||
|
(let [new-state (update state :composite d/remove-at-index idx)]
|
||||||
|
(reset! shadows* (:composite new-state))
|
||||||
|
new-state)))))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps shadows)
|
||||||
|
(fn []
|
||||||
|
(doseq [[idx shadow] (d/enumerate shadows)]
|
||||||
|
(when-not (contains? shadow-colors idx)
|
||||||
|
(let [resolved-color (get-in token-resolve-result [:resolved-value idx :color])
|
||||||
|
initial-color (or resolved-color (get shadow :color) "")]
|
||||||
|
(swap! shadow-colors* assoc idx initial-color))))))
|
||||||
|
|
||||||
|
[:div {:class (stl/css :nested-input-row)}
|
||||||
|
(for [[shadow-idx shadow] (d/enumerate shadows)
|
||||||
|
:let [is-remove-disabled (= shadows-count 1)
|
||||||
|
key (str shadows-count shadow-idx)
|
||||||
|
errors-by-key (when composite-token?
|
||||||
|
(sd/collect-shadow-errors token-resolve-result shadow-idx))]]
|
||||||
|
[:div {:key key
|
||||||
|
:class (stl/css :nested-input-row)}
|
||||||
|
[:> shadow-input-fields*
|
||||||
|
{:is-remove-disabled is-remove-disabled
|
||||||
|
:shadow-idx shadow-idx
|
||||||
|
:on-add-shadow on-add-shadow
|
||||||
|
:on-remove-shadow on-remove-shadow
|
||||||
|
:shadow shadow
|
||||||
|
:on-update-value on-update-value
|
||||||
|
:token-resolve-result token-resolve-result
|
||||||
|
:errors-by-key errors-by-key
|
||||||
|
:on-external-update-value on-external-update-value
|
||||||
|
:shadow-color (get shadow-colors shadow-idx "")}]])]))
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-input-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-m);
|
||||||
|
}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
(ns app.main.ui.workspace.tokens.management.create.shared.color-picker
|
||||||
|
(:require
|
||||||
|
[app.common.types.color :as c]
|
||||||
|
[app.main.data.tinycolor :as tinycolor]
|
||||||
|
[app.main.ui.workspace.colorpicker :as colorpicker]
|
||||||
|
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
||||||
|
[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.util.dom :as dom]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
;; FIXME: this function has confusing name
|
||||||
|
(defn- hex->value
|
||||||
|
[hex]
|
||||||
|
(when-let [tc (tinycolor/valid-color hex)]
|
||||||
|
(let [hex (tinycolor/->hex-string tc)
|
||||||
|
alpha (tinycolor/alpha tc)
|
||||||
|
[r g b] (c/hex->rgb hex)
|
||||||
|
[h s v] (c/hex->hsv hex)]
|
||||||
|
{:hex hex
|
||||||
|
:r r :g g :b b
|
||||||
|
:h h :s s :v v
|
||||||
|
:alpha alpha})))
|
||||||
|
|
||||||
|
(mf/defc ramp*
|
||||||
|
[{:keys [color on-change]}]
|
||||||
|
(let [wrapper-node-ref (mf/use-ref nil)
|
||||||
|
dragging-ref (mf/use-ref false)
|
||||||
|
|
||||||
|
on-start-drag
|
||||||
|
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
|
||||||
|
|
||||||
|
on-finish-drag
|
||||||
|
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
|
||||||
|
|
||||||
|
internal-color*
|
||||||
|
(mf/use-state #(hex->value color))
|
||||||
|
|
||||||
|
internal-color
|
||||||
|
(deref internal-color*)
|
||||||
|
|
||||||
|
on-change'
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-change)
|
||||||
|
(fn [{:keys [hex alpha] :as selector-color}]
|
||||||
|
(let [dragging? (mf/ref-val dragging-ref)]
|
||||||
|
(when-not (and dragging? hex)
|
||||||
|
(reset! internal-color* selector-color)
|
||||||
|
(on-change hex alpha)))))]
|
||||||
|
|
||||||
|
(mf/use-effect
|
||||||
|
(mf/deps color)
|
||||||
|
(fn []
|
||||||
|
;; Update internal color when user changes input value
|
||||||
|
(when-let [color (tinycolor/valid-color color)]
|
||||||
|
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
|
||||||
|
(reset! internal-color* (hex->value color))))))
|
||||||
|
|
||||||
|
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
|
||||||
|
[:div {:ref wrapper-node-ref}
|
||||||
|
[:> ramp-selector*
|
||||||
|
{:color internal-color
|
||||||
|
:on-start-drag on-start-drag
|
||||||
|
:on-finish-drag on-finish-drag
|
||||||
|
:on-change on-change'}]]))
|
||||||
|
|
||||||
|
(mf/defc color-picker*
|
||||||
|
[{:keys [placeholder label default-value input-ref on-blur on-update-value on-external-update-value custom-input-token-value-props token-resolve-result]}]
|
||||||
|
(let [{:keys [color on-display-colorpicker]} custom-input-token-value-props
|
||||||
|
color-ramp-open* (mf/use-state false)
|
||||||
|
color-ramp-open? (deref color-ramp-open*)
|
||||||
|
|
||||||
|
on-click-swatch
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps color-ramp-open? on-display-colorpicker)
|
||||||
|
(fn []
|
||||||
|
(let [open? (not color-ramp-open?)]
|
||||||
|
(reset! color-ramp-open* open?)
|
||||||
|
(when on-display-colorpicker
|
||||||
|
(on-display-colorpicker open?)))))
|
||||||
|
|
||||||
|
swatch
|
||||||
|
(mf/html
|
||||||
|
[:> input-token-color-bullet*
|
||||||
|
{:color color
|
||||||
|
:on-click on-click-swatch}])
|
||||||
|
|
||||||
|
on-change'
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps color on-external-update-value)
|
||||||
|
(fn [hex-value alpha]
|
||||||
|
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
|
||||||
|
prev-input-color (some-> (dom/get-value (mf/ref-val input-ref))
|
||||||
|
(tinycolor/valid-color))
|
||||||
|
;; If the input is a reference we will take the format from the computed value
|
||||||
|
prev-computed-color (when-not prev-input-color
|
||||||
|
(some-> color (tinycolor/valid-color)))
|
||||||
|
prev-format (some-> (or prev-input-color prev-computed-color)
|
||||||
|
(tinycolor/color-format))
|
||||||
|
to-rgba? (and
|
||||||
|
(< alpha 1)
|
||||||
|
(or (= prev-format "hex") (not prev-format)))
|
||||||
|
to-hex? (and (not prev-format) (= alpha 1))
|
||||||
|
format (cond
|
||||||
|
to-rgba? "rgba"
|
||||||
|
to-hex? "hex"
|
||||||
|
prev-format prev-format
|
||||||
|
:else "hex")
|
||||||
|
color-value (-> (tinycolor/valid-color hex-value)
|
||||||
|
(tinycolor/set-alpha (or alpha 1))
|
||||||
|
(tinycolor/->string format))]
|
||||||
|
(dom/set-value! (mf/ref-val input-ref) color-value)
|
||||||
|
(on-external-update-value color-value))))]
|
||||||
|
|
||||||
|
[:*
|
||||||
|
[:> input-token*
|
||||||
|
{:placeholder placeholder
|
||||||
|
:label label
|
||||||
|
:default-value default-value
|
||||||
|
:ref input-ref
|
||||||
|
:on-blur on-blur
|
||||||
|
:on-change on-update-value
|
||||||
|
:slot-start swatch}]
|
||||||
|
(when color-ramp-open?
|
||||||
|
[:> ramp*
|
||||||
|
{:color (some-> color (tinycolor/valid-color))
|
||||||
|
:on-change on-change'}])
|
||||||
|
[:> token-value-hint* {:result token-resolve-result}]]))
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
(ns app.main.ui.workspace.tokens.management.create.shared.composite-tabs
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||||
|
[app.main.ui.icons :as deprecated-icon]
|
||||||
|
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token*]]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(mf/defc composite-reference-input*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [default-value on-blur on-update-value token-resolve-result reference-label reference-icon is-reference-fn]}]
|
||||||
|
[:> input-token*
|
||||||
|
{:aria-label (tr "labels.reference")
|
||||||
|
:placeholder reference-label
|
||||||
|
:icon reference-icon
|
||||||
|
:default-value (when (is-reference-fn default-value) default-value)
|
||||||
|
:on-blur on-blur
|
||||||
|
:on-change on-update-value
|
||||||
|
:token-resolve-result (when (or
|
||||||
|
(:errors token-resolve-result)
|
||||||
|
(string? (:value token-resolve-result)))
|
||||||
|
token-resolve-result)}])
|
||||||
|
|
||||||
|
(mf/defc composite-tabs*
|
||||||
|
[{:keys [default-value
|
||||||
|
on-update-value
|
||||||
|
on-external-update-value
|
||||||
|
on-value-resolve
|
||||||
|
clear-resolve-value
|
||||||
|
custom-input-token-value-props]
|
||||||
|
:rest props}]
|
||||||
|
(let [;; Active Tab State
|
||||||
|
{:keys [active-tab
|
||||||
|
composite-tab
|
||||||
|
is-reference-fn
|
||||||
|
reference-icon
|
||||||
|
reference-label
|
||||||
|
set-active-tab
|
||||||
|
title
|
||||||
|
update-composite-backup-value]} custom-input-token-value-props
|
||||||
|
reference-tab-active? (= :reference active-tab)
|
||||||
|
;; Backup value ref
|
||||||
|
;; Used to restore the previously entered value when switching tabs
|
||||||
|
;; Uses ref to not trigger state updates during update
|
||||||
|
backup-state-ref (mf/use-var
|
||||||
|
(if reference-tab-active?
|
||||||
|
{:reference default-value}
|
||||||
|
{:composite default-value}))
|
||||||
|
default-value (get @backup-state-ref active-tab)
|
||||||
|
|
||||||
|
on-toggle-tab
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps active-tab on-external-update-value on-value-resolve clear-resolve-value)
|
||||||
|
(fn []
|
||||||
|
(let [next-tab (if (= active-tab :composite) :reference :composite)]
|
||||||
|
;; Clear the resolved value so it wont show up before the next-tab value has resolved
|
||||||
|
(clear-resolve-value)
|
||||||
|
;; Restore the internal value from backup
|
||||||
|
(on-external-update-value (get @backup-state-ref next-tab))
|
||||||
|
(set-active-tab next-tab))))
|
||||||
|
|
||||||
|
update-composite-value
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [f]
|
||||||
|
(clear-resolve-value)
|
||||||
|
(swap! backup-state-ref f)
|
||||||
|
(on-external-update-value (get @backup-state-ref :composite))))
|
||||||
|
|
||||||
|
;; Store updated value in backup-state-ref
|
||||||
|
on-update-value'
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-update-value reference-tab-active? update-composite-backup-value)
|
||||||
|
(fn [e]
|
||||||
|
(if reference-tab-active?
|
||||||
|
(swap! backup-state-ref assoc :reference (dom/get-target-val e))
|
||||||
|
(swap! backup-state-ref update :composite #(update-composite-backup-value % e)))
|
||||||
|
(on-update-value e)))]
|
||||||
|
[:div {:class (stl/css :typography-inputs-row)}
|
||||||
|
[:div {:class (stl/css :title-bar)}
|
||||||
|
[:div {:class (stl/css :title)} title]
|
||||||
|
[:& radio-buttons {:selected (if reference-tab-active? "reference" "composite")
|
||||||
|
:on-change on-toggle-tab
|
||||||
|
:name "reference-composite-tab"}
|
||||||
|
[:& radio-button {:icon deprecated-icon/layers
|
||||||
|
:value "composite"
|
||||||
|
:title (tr "workspace.tokens.individual-tokens")
|
||||||
|
:id "composite-opt"}]
|
||||||
|
[:& radio-button {:icon deprecated-icon/tokens
|
||||||
|
:value "reference"
|
||||||
|
:title (tr "workspace.tokens.use-reference")
|
||||||
|
:id "reference-opt"}]]]
|
||||||
|
[:div {:class (stl/css :typography-inputs)}
|
||||||
|
(if reference-tab-active?
|
||||||
|
[:> composite-reference-input*
|
||||||
|
(mf/spread-props props {:default-value default-value
|
||||||
|
:on-update-value on-update-value'
|
||||||
|
:reference-icon reference-icon
|
||||||
|
:reference-label reference-label
|
||||||
|
:is-reference-fn is-reference-fn})]
|
||||||
|
[:> composite-tab
|
||||||
|
(mf/spread-props props {:default-value default-value
|
||||||
|
:on-update-value on-update-value'
|
||||||
|
:update-composite-value update-composite-value})])]]))
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
// 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/_borders.scss" as *;
|
||||||
|
|
||||||
|
.typography-inputs-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typography-inputs {
|
||||||
|
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
||||||
|
padding-inline-start: var(--sp-m);
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
(ns app.main.ui.workspace.tokens.management.create.shared.font-combobox
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.common.types.token :as cto]
|
||||||
|
[app.main.fonts :as fonts]
|
||||||
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
|
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
|
||||||
|
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token*]]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
(mf/defc font-selector-wrapper*
|
||||||
|
{::mf/private true}
|
||||||
|
[{:keys [font input-ref on-select-font on-close-font-selector]}]
|
||||||
|
(let [current-font* (mf/use-state (or font
|
||||||
|
(some-> (mf/ref-val input-ref)
|
||||||
|
(dom/get-value)
|
||||||
|
(cto/split-font-family)
|
||||||
|
(first)
|
||||||
|
(fonts/find-font-family))))
|
||||||
|
current-font (deref current-font*)]
|
||||||
|
[:div {:class (stl/css :font-select-wrapper)}
|
||||||
|
[:> font-selector* {:current-font current-font
|
||||||
|
:on-select on-select-font
|
||||||
|
:on-close on-close-font-selector
|
||||||
|
:full-size true}]]))
|
||||||
|
|
||||||
|
(mf/defc font-picker-combobox*
|
||||||
|
[{:keys [default-value label aria-label input-ref on-blur on-update-value on-external-update-value token-resolve-result placeholder]}]
|
||||||
|
(let [font* (mf/use-state (fonts/find-font-family default-value))
|
||||||
|
font (deref font*)
|
||||||
|
set-font (mf/use-fn
|
||||||
|
(mf/deps font)
|
||||||
|
#(reset! font* %))
|
||||||
|
|
||||||
|
font-selector-open* (mf/use-state false)
|
||||||
|
font-selector-open? (deref font-selector-open*)
|
||||||
|
|
||||||
|
on-close-font-selector
|
||||||
|
(mf/use-fn
|
||||||
|
(fn []
|
||||||
|
(reset! font-selector-open* false)))
|
||||||
|
|
||||||
|
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?))))
|
||||||
|
|
||||||
|
on-select-font
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-external-update-value set-font font)
|
||||||
|
(fn [{:keys [family] :as font}]
|
||||||
|
(when font
|
||||||
|
(set-font font)
|
||||||
|
(on-external-update-value family))))
|
||||||
|
|
||||||
|
on-update-value'
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-update-value set-font)
|
||||||
|
(fn [value]
|
||||||
|
(set-font nil)
|
||||||
|
(on-update-value value)))
|
||||||
|
|
||||||
|
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"}])]
|
||||||
|
[:*
|
||||||
|
[:> input-token*
|
||||||
|
{:placeholder (or placeholder (tr "workspace.tokens.token-font-family-value-enter"))
|
||||||
|
:label label
|
||||||
|
:aria-label aria-label
|
||||||
|
:default-value (or (:name font) default-value)
|
||||||
|
:ref input-ref
|
||||||
|
:on-blur on-blur
|
||||||
|
:on-change on-update-value'
|
||||||
|
:icon i/text-font-family
|
||||||
|
:slot-end font-selector-button
|
||||||
|
:token-resolve-result token-resolve-result}]
|
||||||
|
(when font-selector-open?
|
||||||
|
[:> font-selector-wrapper* {:font font
|
||||||
|
:input-ref input-ref
|
||||||
|
:on-select-font on-select-font
|
||||||
|
:on-close-font-selector on-close-font-selector}])]))
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
.font-select-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
// This padding from the modal should be shared as a variable
|
||||||
|
// Need to set this or the font-select will cause scroll
|
||||||
|
bottom: var(--sp-xxxl);
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
(ns app.main.ui.workspace.tokens.management.create.typography-composite
|
||||||
|
(:require-macros [app.main.style :as stl])
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.types.token :as cto]
|
||||||
|
[app.main.data.style-dictionary :as sd]
|
||||||
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
|
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token*]]
|
||||||
|
[app.main.ui.workspace.tokens.management.create.shared.font-combobox :refer [font-picker-combobox*]]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
|
[app.util.object :as obj]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
|
||||||
|
(def ^:private typography-inputs
|
||||||
|
#(d/ordered-map
|
||||||
|
:font-family
|
||||||
|
{:label (tr "workspace.tokens.token-font-family-value")
|
||||||
|
:icon i/text-font-family
|
||||||
|
:placeholder (tr "workspace.tokens.token-font-family-value-enter")}
|
||||||
|
:font-size
|
||||||
|
{:label "Font Size"
|
||||||
|
:icon i/text-font-size
|
||||||
|
:placeholder (tr "workspace.tokens.font-size-value-enter")}
|
||||||
|
:font-weight
|
||||||
|
{:label "Font Weight"
|
||||||
|
:icon i/text-font-weight
|
||||||
|
:placeholder (tr "workspace.tokens.font-weight-value-enter")}
|
||||||
|
:line-height
|
||||||
|
{:label "Line Height"
|
||||||
|
:icon i/text-lineheight
|
||||||
|
:placeholder (tr "workspace.tokens.line-height-value-enter")}
|
||||||
|
:letter-spacing
|
||||||
|
{:label "Letter Spacing"
|
||||||
|
:icon i/text-letterspacing
|
||||||
|
:placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")}
|
||||||
|
:text-case
|
||||||
|
{:label "Text Case"
|
||||||
|
:icon i/text-mixed
|
||||||
|
:placeholder (tr "workspace.tokens.text-case-value-enter")}
|
||||||
|
:text-decoration
|
||||||
|
{:label "Text Decoration"
|
||||||
|
:icon i/text-underlined
|
||||||
|
:placeholder (tr "workspace.tokens.text-decoration-value-enter")}))
|
||||||
|
|
||||||
|
(mf/defc typography-value-inputs*
|
||||||
|
[{:keys [default-value on-blur on-update-value token-resolve-result]}]
|
||||||
|
(let [composite-token? (not (cto/typography-composite-token-reference? (:value token-resolve-result)))
|
||||||
|
typography-inputs (mf/use-memo typography-inputs)
|
||||||
|
errors-by-key (sd/collect-typography-errors token-resolve-result)]
|
||||||
|
[:div {:class (stl/css :nested-input-row)}
|
||||||
|
(for [[token-type {:keys [label placeholder icon]}] typography-inputs]
|
||||||
|
(let [value (get default-value token-type)
|
||||||
|
resolved (get-in token-resolve-result [:resolved-value token-type])
|
||||||
|
errors (get errors-by-key token-type)
|
||||||
|
|
||||||
|
should-show? (or (and (some? resolved)
|
||||||
|
(not= value (str resolved)))
|
||||||
|
(seq errors))
|
||||||
|
|
||||||
|
token-prop (when (and composite-token? should-show?)
|
||||||
|
(d/without-nils
|
||||||
|
{:resolved-value (when-not (str/empty? resolved) resolved)
|
||||||
|
:errors errors}))
|
||||||
|
|
||||||
|
input-ref (mf/use-ref)
|
||||||
|
|
||||||
|
on-external-update-value
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps on-update-value)
|
||||||
|
(fn [next-value]
|
||||||
|
(let [element (mf/ref-val input-ref)]
|
||||||
|
(dom/set-value! element next-value)
|
||||||
|
(on-update-value #js {:target element
|
||||||
|
:tokenType :font-family}))))
|
||||||
|
|
||||||
|
on-change
|
||||||
|
(mf/use-fn
|
||||||
|
(mf/deps token-type)
|
||||||
|
;; Passing token-type via event to prevent deep function adapting & passing of type
|
||||||
|
(fn [event]
|
||||||
|
(-> (obj/set! event "tokenType" token-type)
|
||||||
|
(on-update-value))))]
|
||||||
|
|
||||||
|
[:div {:key (str token-type)
|
||||||
|
:class (stl/css :input-row)}
|
||||||
|
(case token-type
|
||||||
|
:font-family
|
||||||
|
[:> font-picker-combobox*
|
||||||
|
{:aria-label label
|
||||||
|
:placeholder placeholder
|
||||||
|
:input-ref input-ref
|
||||||
|
:default-value (when value (cto/join-font-family value))
|
||||||
|
:on-blur on-blur
|
||||||
|
:on-update-value on-change
|
||||||
|
:on-external-update-value on-external-update-value
|
||||||
|
:token-resolve-result token-prop}]
|
||||||
|
[:> input-token*
|
||||||
|
{:aria-label label
|
||||||
|
:placeholder placeholder
|
||||||
|
:default-value value
|
||||||
|
:on-blur on-blur
|
||||||
|
:icon icon
|
||||||
|
:on-change on-change
|
||||||
|
:token-resolve-result token-prop}])]))]))
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-input-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-m);
|
||||||
|
}
|
||||||
@ -38,6 +38,7 @@
|
|||||||
:opacity "percentage"
|
:opacity "percentage"
|
||||||
:number "number"
|
:number "number"
|
||||||
:rotation "rotation"
|
:rotation "rotation"
|
||||||
|
:shadow "drop-shadow"
|
||||||
:spacing "padding-extended"
|
:spacing "padding-extended"
|
||||||
:string "text-mixed"
|
:string "text-mixed"
|
||||||
:stroke-width "stroke-size"
|
:stroke-width "stroke-size"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user