mirror of
https://github.com/penpot/penpot.git
synced 2026-02-25 11:19:28 -05:00
Compare commits
5 Commits
eva-fix-sh
...
eva-create
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
155683dbe0 | ||
|
|
3e4b0124c5 | ||
|
|
f22633d18b | ||
|
|
942149ae87 | ||
|
|
ed379013ef |
@@ -1318,7 +1318,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// User adds a second shadow
|
||||
const addButton = firstShadowFields.getByTestId("shadow-add-button");
|
||||
const addButton = firstShadowFields.getByTestId("shadow-add-button-0");
|
||||
await addButton.click();
|
||||
|
||||
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
@@ -1327,7 +1327,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(secondShadowFields).toBeVisible();
|
||||
|
||||
// User adds a third shadow
|
||||
const addButton2 = secondShadowFields.getByTestId("shadow-add-button");
|
||||
const addButton2 = secondShadowFields.getByTestId("shadow-add-button-1");
|
||||
await addButton2.click();
|
||||
|
||||
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
@@ -1353,7 +1353,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await removeButton2.click();
|
||||
|
||||
// Verify second shadow is removed
|
||||
await expect(secondShadowFields.getByTestId("shadow-add-button")).not.toBeVisible();
|
||||
await expect(secondShadowFields.getByTestId("shadow-add-button-3")).not.toBeVisible();
|
||||
|
||||
// Verify that the first shadow kept its values
|
||||
const firstOffsetXValue = await firstShadowFields.getByLabel("X").inputValue();
|
||||
|
||||
@@ -112,14 +112,13 @@
|
||||
(reset! focused-id* nil)
|
||||
(reset! is-open* false)
|
||||
(when (fn? on-change)
|
||||
(on-change id event)))))
|
||||
(on-change id)))))
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps disabled)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(when-not disabled
|
||||
(swap! is-open* not))))
|
||||
|
||||
@@ -196,6 +195,7 @@
|
||||
(mf/set-ref-val! options-ref options))
|
||||
|
||||
[:div {:class (stl/css :select-wrapper)
|
||||
:on-click on-click
|
||||
:ref select-ref
|
||||
:on-blur on-blur}
|
||||
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
|
||||
|
||||
(mf/defc options-dropdown*
|
||||
{::mf/schema schema:options-dropdown}
|
||||
;; TODO: Review schema
|
||||
;; {::mf/schema schema:options-dropdown}
|
||||
[{:keys [ref on-click options selected focused empty-to-end align] :rest props}]
|
||||
(let [align
|
||||
(d/nilv align :left)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
[:focused {:optional true} :boolean]])
|
||||
|
||||
(mf/defc token-option*
|
||||
{::mf/schema schema:token-option}
|
||||
;; {::mf/schema schema:token-option}
|
||||
[{:keys [id name on-click selected ref focused resolved] :rest props}]
|
||||
(let [internal-id (mf/use-id)
|
||||
id (d/nilv id internal-id)]
|
||||
@@ -56,5 +56,12 @@
|
||||
[:span {:aria-labelledby (dm/str id "-name")}
|
||||
name]]
|
||||
(when resolved
|
||||
[:> :span {:class (stl/css :option-pill)}
|
||||
resolved])]))
|
||||
(cond
|
||||
(map? resolved)
|
||||
(for [[k v] resolved]
|
||||
[:div {:key (str k)}
|
||||
[:span (dm/str (d/name k) ": ")]
|
||||
[:strong (str v)]])
|
||||
:else
|
||||
[:span {:class (stl/css :option-pill)}
|
||||
resolved]))]))
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
handle-change-tab
|
||||
(mf/use-fn
|
||||
(mf/deps from on-change-section)
|
||||
(fn [new-section _e ]
|
||||
(fn [new-section]
|
||||
(reset! section (keyword new-section))
|
||||
(when on-change-section
|
||||
(on-change-section (keyword new-section))
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
handle-change-color-space
|
||||
(mf/use-fn
|
||||
(fn [color-space _e]
|
||||
(fn [color-space]
|
||||
(reset! color-space* color-space)))
|
||||
|
||||
color-spaces
|
||||
|
||||
@@ -155,61 +155,65 @@
|
||||
|
||||
tabs-action-button
|
||||
(mf/with-memo []
|
||||
(mf/html [:> collapse-button* {}]))]
|
||||
(mf/html [:> collapse-button* {}]))
|
||||
|
||||
[:> (mf/provider muc/sidebar) {:value :left}
|
||||
[:aside {:ref parent-ref
|
||||
:id "left-sidebar-aside"
|
||||
:data-testid "left-sidebar"
|
||||
:data-left-sidebar-width (str width)
|
||||
:class aside-class
|
||||
:style {:--left-sidebar-width (dm/str width "px")}}
|
||||
active-tokens-by-type
|
||||
(mf/with-memo [resolved-active-tokens]
|
||||
(delay (ctob/group-by-type resolved-active-tokens)))]
|
||||
|
||||
[:> left-header*
|
||||
{:file file
|
||||
:layout layout
|
||||
:project project
|
||||
:page-id page-id
|
||||
:class (stl/css :left-header)}]
|
||||
[:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type}
|
||||
[:> (mf/provider muc/sidebar) {:value :left}
|
||||
[:aside {:ref parent-ref
|
||||
:id "left-sidebar-aside"
|
||||
:data-testid "left-sidebar"
|
||||
:data-left-sidebar-width (str width)
|
||||
:class aside-class
|
||||
:style {:--left-sidebar-width (dm/str width "px")}}
|
||||
|
||||
[:div {:on-pointer-down on-pointer-down
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-pointer-move on-pointer-move
|
||||
:class (stl/css :resize-area)}]
|
||||
[:> left-header*
|
||||
{:file file
|
||||
:layout layout
|
||||
:project project
|
||||
:page-id page-id
|
||||
:class (stl/css :left-header)}]
|
||||
|
||||
(cond
|
||||
(true? shortcuts?)
|
||||
[:> shortcuts-container* {:class (stl/css :settings-bar-content)}]
|
||||
[:div {:on-pointer-down on-pointer-down
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-pointer-move on-pointer-move
|
||||
:class (stl/css :resize-area)}]
|
||||
|
||||
(true? show-debug?)
|
||||
[:> debug-panel* {:class (stl/css :settings-bar-content)}]
|
||||
(cond
|
||||
(true? shortcuts?)
|
||||
[:> shortcuts-container* {:class (stl/css :settings-bar-content)}]
|
||||
|
||||
:else
|
||||
[:div {:class (stl/css :settings-bar-content)}
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default "layers"
|
||||
:selected (name section)
|
||||
:on-change on-tab-change
|
||||
:class (stl/css :left-sidebar-tabs)
|
||||
:action-button-position "start"
|
||||
:action-button tabs-action-button}
|
||||
(true? show-debug?)
|
||||
[:> debug-panel* {:class (stl/css :settings-bar-content)}]
|
||||
|
||||
(case section
|
||||
:assets
|
||||
[:> assets-toolbox*
|
||||
{:size (- width 58)
|
||||
:file-id file-id}]
|
||||
:else
|
||||
[:div {:class (stl/css :settings-bar-content)}
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default "layers"
|
||||
:selected (name section)
|
||||
:on-change on-tab-change
|
||||
:class (stl/css :left-sidebar-tabs)
|
||||
:action-button-position "start"
|
||||
:action-button tabs-action-button}
|
||||
|
||||
:tokens
|
||||
[:> tokens-sidebar-tab*
|
||||
{:tokens-lib tokens-lib
|
||||
:active-tokens active-tokens
|
||||
:resolved-active-tokens resolved-active-tokens}]
|
||||
(case section
|
||||
:assets
|
||||
[:> assets-toolbox*
|
||||
{:size (- width 58)
|
||||
:file-id file-id}]
|
||||
|
||||
:layers
|
||||
[:> layers-content*
|
||||
{:layout layout
|
||||
:width width}])]])]]))
|
||||
:tokens
|
||||
[:> tokens-sidebar-tab*
|
||||
{:tokens-lib tokens-lib
|
||||
:active-tokens active-tokens}]
|
||||
|
||||
:layers
|
||||
[:> layers-content*
|
||||
{:layout layout
|
||||
:width width}])]])]]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@
|
||||
switch-component
|
||||
(mf/use-fn
|
||||
(mf/deps shapes)
|
||||
(fn [pos val _e]
|
||||
(fn [pos val]
|
||||
(if (= val mixed-label)
|
||||
(reset! key* (uuid/next))
|
||||
(let [error-msg (if (> (count shapes) 1)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.workspace.tokens.management.context-menu :refer [token-context-menu]]
|
||||
@@ -115,7 +116,9 @@
|
||||
|
||||
[empty-group filled-group]
|
||||
(mf/with-memo [tokens-by-type]
|
||||
(get-sorted-token-groups tokens-by-type))]
|
||||
(get-sorted-token-groups tokens-by-type))
|
||||
|
||||
active-theme-tokens (mf/use-ctx muc/active-tokens-by-type)]
|
||||
|
||||
(mf/with-effect [tokens-lib selected-token-set-id]
|
||||
(when (and tokens-lib
|
||||
@@ -150,7 +153,7 @@
|
||||
:selected-ids selected
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens resolved-active-tokens
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:tokens tokens}]))
|
||||
|
||||
(for [type empty-group]
|
||||
@@ -158,5 +161,5 @@
|
||||
:type type
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout :is-selected-inside-layout
|
||||
:active-theme-tokens resolved-active-tokens
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:tokens []}])]))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.color :as c]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
@@ -21,19 +22,22 @@
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.fonts :as fonts]
|
||||
[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.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[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.workspace.tokens.management.create.input-tokens-value :refer [input-token*]]
|
||||
[app.main.ui.workspace.tokens.management.create.shadow :refer [shadow-value-inputs*]]
|
||||
[app.main.ui.workspace.tokens.management.create.shared.color-picker :refer [color-picker*]]
|
||||
[app.main.ui.workspace.tokens.management.create.shared.composite-tabs :refer [composite-tabs*]]
|
||||
[app.main.ui.workspace.tokens.management.create.shared.font-combobox :refer [font-picker-combobox*]]
|
||||
[app.main.ui.workspace.tokens.management.create.typography-composite :refer [typography-value-inputs*]]
|
||||
[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*]]
|
||||
[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]
|
||||
[app.util.functions :as uf]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -115,7 +119,7 @@
|
||||
|
||||
(defn- validate-token-with [token validators]
|
||||
(if-let [error (some (fn [validate] (validate token)) validators)]
|
||||
(rx/throw {:errors [error]})
|
||||
(rx/throw {:errors [error] :pepito "kakota"})
|
||||
(rx/of token)))
|
||||
|
||||
(def ^:private default-validators
|
||||
@@ -147,7 +151,10 @@
|
||||
;; Simple validation of the editing token
|
||||
(rx/mapcat #(validate-token-with % validators))
|
||||
;; Resolving token via StyleDictionary
|
||||
(rx/mapcat #(validate-resolve-token % prev-token tokens)))))
|
||||
(rx/mapcat #(validate-resolve-token % prev-token tokens))
|
||||
(rx/catch (fn [e] (if (contains? e :errors)
|
||||
{:errors (:errors e)}
|
||||
(rx/throw e)))))))
|
||||
|
||||
(defn- check-coll-self-reference
|
||||
"Invalidate a collection of `token-vals` for a self-refernce against `token-name`.,"
|
||||
@@ -290,7 +297,7 @@
|
||||
selected-token-set-id
|
||||
action
|
||||
input-value-placeholder
|
||||
|
||||
tokens
|
||||
;; Callbacks
|
||||
validate-token
|
||||
on-value-resolve
|
||||
@@ -550,8 +557,8 @@
|
||||
|
||||
handle-key-down-save
|
||||
(mf/use-fn
|
||||
(mf/deps on-submit)
|
||||
(fn [e]
|
||||
(mf/deps on-submit)
|
||||
(when (k/enter? e)
|
||||
(on-submit e))))]
|
||||
|
||||
@@ -618,8 +625,11 @@
|
||||
:label label
|
||||
:default-value default-value
|
||||
:ref ref
|
||||
:tokens tokens
|
||||
:type token-type
|
||||
:on-blur on-update-value
|
||||
:on-change on-update-value
|
||||
:on-external-update-value on-external-update-value
|
||||
:token-resolve-result token-resolve-result}]))]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input* {:label (tr "workspace.tokens.token-description")
|
||||
@@ -655,25 +665,126 @@
|
||||
:disabled disabled?}
|
||||
(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 tokens]}]
|
||||
[:> 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
|
||||
:tokens tokens
|
||||
:type "composite-reference"
|
||||
: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
|
||||
tokens
|
||||
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
|
||||
:tokens tokens
|
||||
: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*
|
||||
"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."
|
||||
[{:keys [token is-reference-fn composite-tab reference-icon title update-composite-backup-value type on-add-shadow] :rest props}]
|
||||
[{:keys [token is-reference-fn composite-tab reference-icon title update-composite-backup-value tokens] :rest props}]
|
||||
(let [active-tab* (mf/use-state (if (is-reference-fn (:value token)) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
|
||||
custom-input-token-value-props
|
||||
(mf/use-memo
|
||||
(mf/deps active-tab composite-tab reference-icon title update-composite-backup-value is-reference-fn type)
|
||||
(mf/deps active-tab composite-tab reference-icon title update-composite-backup-value is-reference-fn)
|
||||
(fn []
|
||||
{:active-tab active-tab
|
||||
:set-active-tab #(reset! active-tab* %)
|
||||
:composite-tab composite-tab
|
||||
:reference-icon reference-icon
|
||||
:tokens tokens
|
||||
:reference-label (tr "workspace.tokens.reference-composite")
|
||||
:title title
|
||||
:type type
|
||||
:on-add-shadow on-add-shadow
|
||||
:update-composite-backup-value update-composite-backup-value
|
||||
:is-reference-fn is-reference-fn}))
|
||||
|
||||
@@ -693,8 +804,128 @@
|
||||
|
||||
;; 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 tokens]} 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
|
||||
:tokens tokens
|
||||
:type "color"
|
||||
: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*
|
||||
[{:keys [token on-display-colorpicker] :rest props}]
|
||||
[{:keys [token on-display-colorpicker tokens] :rest props}]
|
||||
(let [color* (mf/use-state (:value token))
|
||||
color (deref color*)
|
||||
on-value-resolve (mf/use-fn
|
||||
@@ -708,6 +939,7 @@
|
||||
(mf/deps color on-display-colorpicker)
|
||||
(fn []
|
||||
{:color color
|
||||
:tokens tokens
|
||||
:on-display-colorpicker on-display-colorpicker}))
|
||||
|
||||
on-get-token-value
|
||||
@@ -727,10 +959,242 @@
|
||||
:custom-input-token-value color-picker*
|
||||
: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*
|
||||
[{:keys [token] :rest props}]
|
||||
(let [on-get-token-value
|
||||
(mf/use-fn
|
||||
(mf/use-callback
|
||||
(fn [e prev-composite-value]
|
||||
(let [prev-composite-value (or prev-composite-value [])
|
||||
[idx token-type :as token-type-at-index] (obj/get e "tokenTypeAtIndex")
|
||||
@@ -744,7 +1208,7 @@
|
||||
:else (assoc-in prev-composite-value token-type-at-index input-value)))))
|
||||
|
||||
update-composite-backup-value
|
||||
(mf/use-fn
|
||||
(mf/use-callback
|
||||
(fn [prev-composite-value e]
|
||||
(let [[idx token-type :as token-type-at-index] (obj/get e "tokenTypeAtIndex")
|
||||
token-value (case token-type
|
||||
@@ -756,19 +1220,7 @@
|
||||
(if valid?
|
||||
(assoc-in (or prev-composite-value []) token-type-at-index token-value)
|
||||
;; Remove empty values so they don't retrigger validation when switching tabs
|
||||
(update prev-composite-value idx dissoc token-type)))))
|
||||
|
||||
on-add-shadow
|
||||
#(prn "Add shadow clicked")
|
||||
;; (mf/use-fn
|
||||
;; (mf/deps shadows update-composite-value)
|
||||
;; (fn []
|
||||
;; (update-composite-backup-value
|
||||
;; (fn [state]
|
||||
;; (let [new-state (update state :composite (fnil conj []) {})]
|
||||
;; (reset! shadows* (:composite new-state))
|
||||
;; new-state)))))
|
||||
]
|
||||
(update prev-composite-value idx dissoc token-type)))))]
|
||||
[:> composite-form*
|
||||
(mf/spread-props props {:token token
|
||||
:composite-tab shadow-value-inputs*
|
||||
@@ -776,11 +1228,90 @@
|
||||
:is-reference-fn cto/typography-composite-token-reference?
|
||||
:title (tr "workspace.tokens.shadow-title")
|
||||
:validate-token validate-shadow-token
|
||||
:type :shadow
|
||||
:on-add-shadow on-add-shadow
|
||||
:on-get-token-value on-get-token-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 [tokens 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'
|
||||
:tokens tokens
|
||||
:type "font-family"
|
||||
: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*
|
||||
[{:keys [token] :rest props}]
|
||||
(let [on-value-resolve
|
||||
@@ -812,8 +1343,103 @@
|
||||
(mf/spread-props props {:token token
|
||||
: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 tokens]}]
|
||||
(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
|
||||
:tokens tokens
|
||||
: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
|
||||
:tokens tokens
|
||||
:icon icon
|
||||
:type "typography-subvalue"
|
||||
:on-change on-change
|
||||
:token-resolve-result token-prop}])]))]))
|
||||
|
||||
(mf/defc typography-form*
|
||||
[{:keys [token] :rest props}]
|
||||
[{:keys [token tokens] :rest props}]
|
||||
(let [on-get-token-value
|
||||
(mf/use-fn
|
||||
(fn [e prev-composite-value]
|
||||
@@ -844,13 +1470,15 @@
|
||||
:is-reference-fn cto/typography-composite-token-reference?
|
||||
:title (tr "labels.typography")
|
||||
:validate-token validate-typography-token
|
||||
:tokens tokens
|
||||
:on-get-token-value on-get-token-value
|
||||
:update-composite-backup-value update-composite-backup-value})]))
|
||||
|
||||
(mf/defc form-wrapper*
|
||||
[{:keys [token token-type] :rest props}]
|
||||
[{:keys [token token-type tokens] :rest props}]
|
||||
(let [token-type' (or (:type token) token-type)
|
||||
props (mf/spread-props props {:token-type token-type'
|
||||
props (mf/spread-props props {:token-type token-type'
|
||||
:tokens tokens
|
||||
:token token})]
|
||||
(case token-type'
|
||||
:color [:> color-form* props]
|
||||
|
||||
@@ -41,6 +41,34 @@
|
||||
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 {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
@@ -7,18 +7,117 @@
|
||||
(ns app.main.ui.workspace.tokens.management.create.input-tokens-value
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.data.workspace.tokens.warnings :as wtw]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]]
|
||||
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
|
||||
[app.main.ui.ds.controls.utilities.input-field :refer [input-field*]]
|
||||
[app.main.ui.ds.controls.utilities.label :refer [label*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon-list]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon-list] :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def token-type->reference-types
|
||||
{:color #{:color}
|
||||
:dimensions #{:dimensions}
|
||||
:spacing #{:spacing :dimensions}
|
||||
:border-radius #{:border-radius :dimensions :sizing}
|
||||
:font-family #{:font-family}
|
||||
:font-size #{:font-size :sizing :dimension}
|
||||
:opacity #{:opacity :number}
|
||||
:rotation #{:rotation :number}
|
||||
:stroke-width #{:stroke-width :dimension :sizing}
|
||||
:sizing #{:sizing :dimensions}
|
||||
:number #{:number}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:typography #{:typography}
|
||||
:text-case #{:text-case}
|
||||
:text-decoration #{:text-decoration}
|
||||
:line-height #{:number}})
|
||||
|
||||
;; TODO; duplucated code with numeric-input.cljs, consider refactoring
|
||||
(defn- sort-groups-and-tokens
|
||||
"Sorts both the groups and the tokens inside them alphabetically.
|
||||
|
||||
Input:
|
||||
A map where:
|
||||
- keys are groups (keywords or strings, e.g. :dimensions, :colors)
|
||||
- values are vectors of token maps, each containing at least a :name key
|
||||
|
||||
Example input:
|
||||
{:dimensions [{:name \"tres\"} {:name \"quini\"}]
|
||||
:colors [{:name \"azul\"} {:name \"rojo\"}]}
|
||||
|
||||
Output:
|
||||
A sorted map where:
|
||||
- groups are ordered alphabetically by key
|
||||
- tokens inside each group are sorted alphabetically by :name
|
||||
|
||||
Example output:
|
||||
{:colors [{:name \"azul\"} {:name \"rojo\"}]
|
||||
:dimensions [{:name \"quini\"} {:name \"tres\"}]}"
|
||||
|
||||
[groups->tokens]
|
||||
(into (sorted-map) ;; ensure groups are ordered alphabetically by their key
|
||||
(for [[group tokens] groups->tokens]
|
||||
[group (sort-by :name tokens)])))
|
||||
|
||||
(defn- extract-partial-brace-text
|
||||
[string]
|
||||
(when-let [start (str/last-index-of string "{")]
|
||||
(subs string (inc start))))
|
||||
|
||||
(defn- filter-token-groups-by-name
|
||||
[tokens filter-text]
|
||||
(let [lc-filter (str/lower filter-text)]
|
||||
(into {}
|
||||
(keep (fn [[group tokens]]
|
||||
(let [filtered (filter #(str/includes? (str/lower (:name %)) lc-filter) tokens)]
|
||||
(when (seq filtered)
|
||||
[group filtered]))))
|
||||
tokens)))
|
||||
|
||||
(defn- token->dropdown-option
|
||||
[token]
|
||||
{:id (str (get token :id))
|
||||
:type :token
|
||||
:resolved-value (get token :resolved-value)
|
||||
:name (get token :name)})
|
||||
|
||||
(defn- generate-dropdown-options
|
||||
[tokens no-sets]
|
||||
(if (empty? tokens)
|
||||
[{:type :empty
|
||||
:label (if no-sets
|
||||
(tr "ds.inputs.numeric-input.no-applicable-tokens")
|
||||
(tr "ds.inputs.numeric-input.no-matches"))}]
|
||||
(->> tokens
|
||||
(map (fn [[type items]]
|
||||
(cons {:group true
|
||||
:type :group
|
||||
:id (dm/str "group-" (name type))
|
||||
:name (name type)}
|
||||
(map token->dropdown-option items))))
|
||||
(interpose [{:separator true
|
||||
:id "separator"
|
||||
:type :separator}])
|
||||
(apply concat)
|
||||
(vec)
|
||||
(not-empty))))
|
||||
(defn get-option
|
||||
[options id]
|
||||
(let [options (if (delay? options) @options options)]
|
||||
(or (d/seek #(= id (get % :id)) options)
|
||||
(nth options 0))))
|
||||
|
||||
(def ^:private schema::input-token
|
||||
[:map
|
||||
[:label {:optional true} [:maybe :string]]
|
||||
@@ -58,21 +157,119 @@
|
||||
(mf/defc input-token*
|
||||
{::mf/forward-ref true
|
||||
::mf/schema schema::input-token}
|
||||
[{:keys [class label token-resolve-result] :rest props} ref]
|
||||
[{:keys [class label token-resolve-result tokens empty-to-end type on-external-update-value] :rest props} ref]
|
||||
(let [error (not (nil? (:errors token-resolve-result)))
|
||||
id (mf/use-id)
|
||||
input-ref (mf/use-ref)
|
||||
|
||||
is-open* (mf/use-state false)
|
||||
is-open (deref is-open*)
|
||||
|
||||
filter-term* (mf/use-state "")
|
||||
filter-term (deref filter-term*)
|
||||
|
||||
listbox-id (mf/use-id)
|
||||
|
||||
focused-id* (mf/use-state nil)
|
||||
focused-id (deref focused-id*)
|
||||
|
||||
selected-id* (mf/use-state (fn []))
|
||||
selected-id (deref selected-id*)
|
||||
|
||||
empty-to-end (d/nilv empty-to-end false)
|
||||
|
||||
internal-ref (mf/use-ref nil)
|
||||
ref (or ref internal-ref)
|
||||
nodes-ref (mf/use-ref nil)
|
||||
open-dropdown-ref (mf/use-ref nil)
|
||||
options-ref (mf/use-ref nil)
|
||||
set-option-ref
|
||||
(mf/use-fn
|
||||
(fn [node]
|
||||
(let [state (mf/ref-val nodes-ref)
|
||||
state (d/nilv state #js {})
|
||||
id (dom/get-data node "id")
|
||||
state (obj/set! state id node)]
|
||||
(mf/set-ref-val! nodes-ref state)
|
||||
(fn []
|
||||
(let [state (mf/ref-val nodes-ref)
|
||||
state (d/nilv state #js {})
|
||||
id (dom/get-data node "id")
|
||||
state (obj/unset! state id)]
|
||||
(mf/set-ref-val! nodes-ref state))))))
|
||||
|
||||
dropdown-options
|
||||
(mf/with-memo [tokens filter-term type]
|
||||
(delay
|
||||
(let [tokens (if (delay? tokens) @tokens tokens)
|
||||
allowed (get token-type->reference-types (keyword type) #{})
|
||||
tokens (select-keys tokens allowed)
|
||||
sorted-tokens (sort-groups-and-tokens tokens)
|
||||
partial (extract-partial-brace-text filter-term)
|
||||
|
||||
options (if (seq partial)
|
||||
(filter-token-groups-by-name sorted-tokens partial)
|
||||
sorted-tokens)
|
||||
no-sets? (nil? sorted-tokens)]
|
||||
(generate-dropdown-options options no-sets?))))
|
||||
|
||||
update-input
|
||||
(mf/use-fn
|
||||
(mf/deps ref)
|
||||
(fn [new-value]
|
||||
(when-let [node (mf/ref-val ref)]
|
||||
(dom/set-value! node new-value)
|
||||
(reset! is-open* false))))
|
||||
|
||||
on-option-click
|
||||
(mf/use-fn
|
||||
(mf/deps options-ref update-input)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (dom/get-data node "id")
|
||||
options (mf/ref-val options-ref)
|
||||
options (if (delay? options) @options options)
|
||||
option (get-option options id)
|
||||
name (get option :name)
|
||||
new-value (str "{" name "}")]
|
||||
(on-external-update-value new-value)
|
||||
(reset! is-open* false))))
|
||||
|
||||
open-dropdown
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
(swap! is-open* not)))
|
||||
|
||||
props (mf/spread-props props {:id id
|
||||
:type "text"
|
||||
:class (stl/css :input)
|
||||
:variant "comfortable"
|
||||
:hint-type (when error "error")
|
||||
:ref (or ref input-ref)})]
|
||||
:slot-end (when (some? tokens)
|
||||
(mf/html [:> icon-button* {:variant "action"
|
||||
:icon i/tokens
|
||||
:class (stl/css :invisible-button)
|
||||
:aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown")
|
||||
:ref open-dropdown-ref
|
||||
:on-click open-dropdown}]))
|
||||
:ref ref})]
|
||||
|
||||
(mf/with-effect [dropdown-options]
|
||||
(mf/set-ref-val! options-ref dropdown-options))
|
||||
[:*
|
||||
[:div {:class (dm/str class " " (stl/css-case :wrapper true
|
||||
:input-error error))}
|
||||
[:div {:class [class (stl/css-case :wrapper true
|
||||
:input-error error)]}
|
||||
(when label
|
||||
[:> label* {:for id} label])
|
||||
[:> input-field* props]]
|
||||
(when token-resolve-result
|
||||
[:> token-value-hint* {:result token-resolve-result}])]))
|
||||
[:> token-value-hint* {:result token-resolve-result}])
|
||||
(when ^boolean is-open
|
||||
(let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)]
|
||||
[:> options-dropdown* {:on-click on-option-click
|
||||
:id listbox-id
|
||||
:options options
|
||||
:selected selected-id
|
||||
:focused focused-id
|
||||
:align :left
|
||||
:empty-to-end empty-to-end
|
||||
:ref set-option-ref}]))]))
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
(clj->js))))
|
||||
|
||||
(mf/defc token-update-create-modal
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [x y position token token-type action selected-token-set-id] :as _args}]
|
||||
{ ::mf/props :obj}
|
||||
[{:keys [x y position token token-type action selected-token-set-id tokens] :as _args}]
|
||||
(let [wrapper-style (use-viewport-position-style x y position (= token-type :color))
|
||||
modal-size-large* (mf/use-state (= token-type :typography))
|
||||
modal-size-large? (deref modal-size-large*)
|
||||
@@ -94,6 +94,7 @@
|
||||
:action action
|
||||
:selected-token-set-id selected-token-set-id
|
||||
:token-type token-type
|
||||
:tokens tokens
|
||||
:on-display-colorpicker update-modal-size}]]))
|
||||
|
||||
;; Modals ----------------------------------------------------------------------
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
;; 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.controls.select :refer [select*]]
|
||||
[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 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)
|
||||
(fn [value evento]
|
||||
(.log js/console (clj->js value))
|
||||
(.log js/console (clj->js evento))
|
||||
(obj/set! evento "tokenValue" (if (= "true" value) true false))
|
||||
|
||||
(on-change evento)
|
||||
(reset! selected* (str value))))
|
||||
|
||||
options
|
||||
(mf/with-memo []
|
||||
[{:id "false" :label "drop-shadow" :icon i/drop-shadow}
|
||||
{:id "true" :label "inner-shadow" :icon i/inner-shadow}])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> select* {:default-selected selected
|
||||
:variant "ghost"
|
||||
:options options
|
||||
:on-change on-change}]]))
|
||||
|
||||
#_(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]
|
||||
(.log js/console (clj->js value))
|
||||
(.log js/console (clj->js 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
|
||||
:inset
|
||||
{:label (tr "workspace.tokens.shadow-inset")
|
||||
:placeholder (tr "workspace.tokens.shadow-inset")}
|
||||
:color
|
||||
{:label (tr "workspace.tokens.shadow-color")
|
||||
:placeholder (tr "workspace.tokens.shadow-color")}
|
||||
: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")}))
|
||||
|
||||
(def ^:private input-icon
|
||||
{:offsetX i/character-x
|
||||
:offsetY i/character-y})
|
||||
|
||||
(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 [evento]
|
||||
(-> (obj/set! evento "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
|
||||
:aria-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 (or shadow-color nil)
|
||||
: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*
|
||||
{:aria-label label
|
||||
:icon (get input-icon input-type)
|
||||
:placeholder placeholder
|
||||
:default-value default-value
|
||||
:on-change on-change
|
||||
:slot-start (cond (= input-type :blur)
|
||||
(mf/html [:span {:class (stl/css :shadow-prop-label)} "Blur"])
|
||||
(= input-type :spread)
|
||||
(mf/html [:span {:class (stl/css :shadow-prop-label)} "Spread"]))
|
||||
: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)
|
||||
:class (stl/css :shadow-input-fields)}
|
||||
[:> 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 "")}]])]))
|
||||
@@ -1,23 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
.shadow-input-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-m);
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
(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*)
|
||||
_ (.log js/console (clj->js token-resolve-result))
|
||||
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'}])
|
||||
(when token-resolve-result
|
||||
[:> token-value-hint* {:result token-resolve-result}])]))
|
||||
@@ -1,115 +0,0 @@
|
||||
(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.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[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
|
||||
type
|
||||
on-add-shadow
|
||||
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"}]]
|
||||
(when (= type :shadow)
|
||||
[:> icon-button* {:icon i/add
|
||||
:type "button"
|
||||
:on-click on-add-shadow
|
||||
:data-testid "shadow-add-button"
|
||||
:aria-label (tr "workspace.tokens.shadow-add-shadow")}])]
|
||||
[: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})])]]))
|
||||
@@ -1,31 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
(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}])]))
|
||||
@@ -1,13 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
(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}])]))]))
|
||||
@@ -1,17 +0,0 @@
|
||||
// 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,7 +38,6 @@
|
||||
:opacity "percentage"
|
||||
:number "number"
|
||||
:rotation "rotation"
|
||||
:shadow "drop-shadow"
|
||||
:spacing "padding-extended"
|
||||
:string "text-mixed"
|
||||
:stroke-width "stroke-size"
|
||||
@@ -78,7 +77,7 @@
|
||||
|
||||
on-popover-open-click
|
||||
(mf/use-fn
|
||||
(mf/deps type title modal)
|
||||
(mf/deps type title modal active-theme-tokens)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dwtl/set-token-type-section-open type true)
|
||||
@@ -87,6 +86,7 @@
|
||||
{:x (:x pos)
|
||||
:y (:y pos)
|
||||
:position :right
|
||||
:tokens active-theme-tokens
|
||||
:fields (:fields modal)
|
||||
:title title
|
||||
:action "create"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.color :as dwtc]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon*]]
|
||||
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
|
||||
@@ -27,7 +27,6 @@
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; Translation dictionaries
|
||||
|
||||
(def ^:private attribute-dictionary
|
||||
{:rotation "Rotation"
|
||||
:opacity "Opacity"
|
||||
@@ -77,8 +76,7 @@
|
||||
:y :y})
|
||||
|
||||
;; Helper functions
|
||||
|
||||
(defn partially-applied-attr
|
||||
(defn- partially-applied-attr
|
||||
"Translates partially applied attributes based on the dictionary."
|
||||
[app-token-keys is-applied {:keys [attributes all-attributes]}]
|
||||
(let [filtered-keys (if all-attributes
|
||||
@@ -87,7 +85,7 @@
|
||||
(when is-applied
|
||||
(str/join ", " (map attribute-dictionary filtered-keys)))))
|
||||
|
||||
(defn translate-and-format
|
||||
(defn- translate-and-format
|
||||
"Translates and formats grouped values by category."
|
||||
[grouped-values]
|
||||
(str/join "\n"
|
||||
@@ -98,6 +96,18 @@
|
||||
(str/join ", " (map attribute-dictionary values)) ".")))
|
||||
grouped-values)))
|
||||
|
||||
(defn- token-exists?
|
||||
"Returns true if any token in the grouped token map has a name matching `token-name`."
|
||||
[tokens-by-type token-name]
|
||||
(let [clean-name (-> token-name
|
||||
(str/trim)
|
||||
(str/replace #"^\{" "")
|
||||
(str/replace #"\}$" "")
|
||||
(str/lower))]
|
||||
(some (fn [[_ tokens]]
|
||||
(some #(= clean-name (:name %)) tokens))
|
||||
tokens-by-type)))
|
||||
|
||||
(defn- generate-tooltip
|
||||
"Generates a tooltip for a given token"
|
||||
[is-viewer shape theme-token token half-applied no-valid-value ref-not-in-active-set]
|
||||
@@ -142,14 +152,6 @@
|
||||
;; Otherwise only show the base title
|
||||
:else base-title)))
|
||||
|
||||
;; FIXME: the token thould already have precalculated references, so
|
||||
;; we don't need to perform this regex operation on each rerender
|
||||
(defn contains-reference-value?
|
||||
"Extracts the value between `{}` in a string and checks if it's in the provided vector."
|
||||
[text active-tokens]
|
||||
(let [match (second (re-find #"\{([^}]+)\}" text))]
|
||||
(contains? active-tokens match)))
|
||||
|
||||
(def ^:private
|
||||
xf:map-id
|
||||
(map :id))
|
||||
@@ -176,7 +178,6 @@
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}]
|
||||
(let [{:keys [name value errors type]} token
|
||||
|
||||
has-selected? (pos? (count selected-shapes))
|
||||
is-reference? (cft/is-reference? token)
|
||||
contains-path? (str/includes? name ".")
|
||||
@@ -203,14 +204,14 @@
|
||||
(not half-applied?)
|
||||
(not (attributes-match-selection? selected-shapes attributes {:selected-inside-layout? is-selected-inside-layout})))
|
||||
|
||||
;; FIXME: move to context or props
|
||||
can-edit? (:can-edit (deref refs/permissions))
|
||||
can-edit?
|
||||
(mf/use-ctx ctx/can-edit?)
|
||||
|
||||
is-viewer? (not can-edit?)
|
||||
|
||||
ref-not-in-active-set
|
||||
(and is-reference?
|
||||
(not (contains-reference-value? value active-theme-tokens)))
|
||||
(not (token-exists? @active-theme-tokens value)))
|
||||
|
||||
no-valid-value (seq errors)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user