mirror of
https://github.com/penpot/penpot.git
synced 2026-05-02 14:48:29 +00:00
♻️ Extract composite component wrapper
This commit is contained in:
parent
73222f22d0
commit
d01df7738a
@ -1006,10 +1006,17 @@ test.describe("Tokens: Themes modal", () => {
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
|
||||
// Empty reference tab should be disabled
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
const compositeTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("composite-opt");
|
||||
await compositeTabButton.click();
|
||||
|
||||
// Filled composite tab should be enabled
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
// Verify all values are preserved after switching tabs
|
||||
await expect(fontSizeField).toHaveValue(originalValues.fontSize);
|
||||
await expect(fontFamilyField).toHaveValue(originalValues.fontFamily);
|
||||
|
||||
@ -261,7 +261,7 @@
|
||||
|
||||
(defonce form-token-cache-atom (atom nil))
|
||||
|
||||
;; Component -------------------------------------------------------------------
|
||||
;; Form Component --------------------------------------------------------------
|
||||
|
||||
(mf/defc form*
|
||||
"Form component to edit or create a token of any token type.
|
||||
@ -636,6 +636,123 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
: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]}]
|
||||
[:> input-token*
|
||||
{:aria-label (tr "labels.reference")
|
||||
:placeholder (tr "workspace.tokens.reference-composite")
|
||||
: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 set-active-tab composite-tab reference-icon title update-composite-backup-value is-reference-fn]} 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))))
|
||||
|
||||
;; 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
|
||||
:is-reference-fn is-reference-fn})]
|
||||
[:> composite-tab
|
||||
(mf/spread-props props {:default-value default-value
|
||||
:on-update-value on-update-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] :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)
|
||||
(fn []
|
||||
{:active-tab active-tab
|
||||
:set-active-tab #(reset! active-tab* %)
|
||||
:composite-tab composite-tab
|
||||
:reference-icon reference-icon
|
||||
:title title
|
||||
:update-composite-backup-value update-composite-backup-value
|
||||
:is-reference-fn is-reference-fn}))
|
||||
|
||||
;; Remove the value from a stored token when it doesn't match the tab type
|
||||
;; We need this to keep the form disabled when there's an existing value that doesn't match the tab type
|
||||
token
|
||||
(mf/use-memo
|
||||
(mf/deps token active-tab is-reference-fn)
|
||||
(fn []
|
||||
(let [token-tab-type (if (is-reference-fn (:value token)) :reference :composite)]
|
||||
(cond-> token
|
||||
(not= token-tab-type active-tab) (dissoc :value token)))))]
|
||||
[:> form*
|
||||
(mf/spread-props props {:token token
|
||||
:custom-input-token-value composite-tabs*
|
||||
:custom-input-token-value-props custom-input-token-value-props})]))
|
||||
|
||||
;; Token Type Forms ------------------------------------------------------------
|
||||
|
||||
;; FIXME: this function has confusing name
|
||||
(defn- hex->value
|
||||
[hex]
|
||||
@ -983,121 +1100,37 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
:on-change on-change
|
||||
:token-resolve-result (when (seq token-resolve-result) token-resolve-result)}])]))]))
|
||||
|
||||
(mf/defc typography-reference-input*
|
||||
[{:keys [default-value on-blur on-update-value token-resolve-result]}]
|
||||
[:> input-token*
|
||||
{:aria-label (tr "labels.reference")
|
||||
:placeholder (tr "workspace.tokens.reference-composite")
|
||||
:icon i/text-typography
|
||||
:default-value (when (cto/typography-composite-token-reference? 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 typography-inputs*
|
||||
[{: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 set-active-tab]} 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))))
|
||||
|
||||
;; Store token value in the backup-state-ref
|
||||
on-update-reference-value
|
||||
(mf/use-fn
|
||||
(mf/deps on-update-value reference-tab-active?)
|
||||
(fn [e]
|
||||
(if reference-tab-active?
|
||||
(swap! backup-state-ref assoc :reference (dom/get-target-val e))
|
||||
(let [token-type (obj/get e "tokenType")
|
||||
token-value (dom/get-target-val e)
|
||||
token-value (cond-> token-value
|
||||
(= :font-family token-type) (cto/split-font-family))]
|
||||
(swap! backup-state-ref assoc-in [:composite token-type] token-value)))
|
||||
(on-update-value e)))
|
||||
|
||||
input-props (mf/spread-props props {:default-value default-value
|
||||
:on-update-value on-update-reference-value})]
|
||||
[:div {:class (stl/css :typography-inputs-row)}
|
||||
[:div {:class (stl/css :title-bar)}
|
||||
[:div {:class (stl/css :title)}
|
||||
(tr "labels.typography")]
|
||||
[:& 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?
|
||||
[:> typography-reference-input* input-props]
|
||||
[:> typography-value-inputs* input-props])]]))
|
||||
|
||||
(mf/defc typography-form*
|
||||
[{:keys [token] :rest props}]
|
||||
(let [active-tab* (mf/use-state (if (cto/typography-composite-token-reference? (:value token)) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
|
||||
custom-input-token-value-props
|
||||
(mf/use-memo
|
||||
(mf/deps active-tab)
|
||||
(fn []
|
||||
{:active-tab active-tab
|
||||
:set-active-tab #(reset! active-tab* %)}))
|
||||
|
||||
;; Remove the value from a stored token when it doesn't match the tab type
|
||||
;; We need this to keep the form disabled when there's an existing value that doesn't match the tab type
|
||||
token
|
||||
(mf/use-memo
|
||||
(mf/deps token active-tab)
|
||||
(fn []
|
||||
(let [token-tab-type (if (cto/typography-composite-token-reference? (:value token)) :reference :composite)]
|
||||
(cond-> token
|
||||
(not= token-tab-type active-tab) (dissoc :value token)))))
|
||||
|
||||
on-get-token-value
|
||||
(let [on-get-token-value
|
||||
(mf/use-callback
|
||||
(fn [e prev-value]
|
||||
(fn [e prev-composite-value]
|
||||
(let [token-type (obj/get e "tokenType")
|
||||
input-value (dom/get-target-val e)
|
||||
reference-value-input? (not token-type)]
|
||||
(cond
|
||||
reference-value-input? input-value
|
||||
|
||||
(empty? input-value) (dissoc prev-value token-type)
|
||||
:else (assoc prev-value token-type input-value)))))]
|
||||
[:> form*
|
||||
(empty? input-value) (dissoc prev-composite-value token-type)
|
||||
:else (assoc prev-composite-value token-type input-value)))))
|
||||
|
||||
update-composite-backup-value
|
||||
(mf/use-callback
|
||||
(fn [prev-composite-value e]
|
||||
(let [token-type (obj/get e "tokenType")
|
||||
token-value (dom/get-target-val e)
|
||||
token-value (cond-> token-value
|
||||
(= :font-family token-type) (cto/split-font-family))]
|
||||
(assoc prev-composite-value token-type token-value))))]
|
||||
[:> composite-form*
|
||||
(mf/spread-props props {:token token
|
||||
:custom-input-token-value typography-inputs*
|
||||
:custom-input-token-value-props custom-input-token-value-props
|
||||
:composite-tab typography-value-inputs*
|
||||
:reference-icon i/text-typography
|
||||
:is-reference-fn cto/typography-composite-token-reference?
|
||||
:title (tr "labels.typography")
|
||||
:validate-token validate-typography-token
|
||||
:on-get-token-value on-get-token-value})]))
|
||||
:on-get-token-value on-get-token-value
|
||||
:update-composite-backup-value update-composite-backup-value})]))
|
||||
|
||||
(mf/defc form-wrapper*
|
||||
[{:keys [token token-type] :as props}]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user