diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index bf1864d461..d0ee5e4f01 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -27,8 +27,10 @@ [app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]] [app.main.ui.ds.controls.shared.searchable-options-dropdown :refer [searchable-options-dropdown*]] [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.ds.foundations.typography :as t] + [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.hooks :as hooks] - [app.main.ui.workspace.sidebar.options.menus.text-shared :refer [text-options]] + [app.main.ui.workspace.sidebar.options.menus.text-shared :refer [text-options token-text-options*]] [app.main.ui.workspace.sidebar.options.menus.token-typography-row :refer [token-typography-row*]] [app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry]] [app.main.ui.workspace.tokens.management.forms.controls.utils :as csu] @@ -189,6 +191,82 @@ :label (tr "workspace.options.text-options.strikethrough" (sc/get-tooltip :line-through)) :icon i/text-stroked}]}]])) +;; We are temporarily duplicating some components and modifying the copies +;; to work under a feature flag. When the flag is set to false, the original +;; components are used; when enabled, the new versions are used instead. +;; +;; This approach introduces some code duplication, but it helps avoid +;; scattering conditional (feature flag) logic throughout the codebase, +;; keeping both implementations easier to read and maintain during the transition. +;; +;; Once the feature flag is fully enabled, the old components will be removed +;; and the duplicated code will be cleaned up. +;; +;; Once the feature flag is fully enabled, the old components will be removed +;; and the duplicated code will be cleaned up. +(mf/defc token-text-decoration-options* + [{:keys [values on-change on-blur token-applied]}] + (let [token-row (contains? cf/flags :token-typography-row) + text-decoration (some-> (:text-decoration values) d/name) + handle-change + (mf/use-fn + (mf/deps on-change on-blur) + (fn [value] + (on-change {:text-decoration value}) + (when (some? on-blur) + (on-blur))))] + + [:div {:class (stl/css :text-decoration-options)} + [:> radio-buttons* {:selected text-decoration + :on-change handle-change + :name "text-decoration-options" + :disabled (and token-row (some? token-applied)) + :options [{:value "none" + :id "none-text-decoration" + :disabled (and token-row (some? token-applied)) + :label "Unset" + :icon i/remove} + {:value "underline" + :id "underline-text-decoration" + :disabled (and token-row (some? token-applied)) + :label (tr "workspace.options.text-options.underline" (sc/get-tooltip :underline)) + :icon i/text-underlined} + {:value "line-through" + :id "line-through-text-decoration" + :disabled (and token-row (some? token-applied)) + :label (tr "workspace.options.text-options.strikethrough" (sc/get-tooltip :line-through)) + :icon i/text-stroked}]}]])) + +(mf/defc text-transform-options* + [{:keys [values on-change on-blur]}] + (let [handle-change + (mf/use-fn + (mf/deps on-change on-blur) + (fn [value] + (on-change {:text-transform value}) + (when (some? on-blur) (on-blur))))] + + [:div {:class (stl/css :text-transform)} + [:> radio-buttons* {:selected (:text-transform values) + :on-change handle-change + :name "text-transform-options" + :options [{:value "none" + :id "text-transform-none" + :label "Unset" + :icon i/remove} + {:value "uppercase" + :id "text-transform-uppercase" + :label (tr "workspace.options.text-options.text-transform-uppercase") + :icon i/text-uppercase} + {:value "capitalize" + :id "text-transform-capitalize" + :label (tr "workspace.options.text-options.text-transform-capitalize") + :icon i/text-mixed} + {:value "lowercase" + :id "text-transform-lowercase" + :label (tr "workspace.options.text-options.text-transform-lowercase") + :icon i/text-lowercase}]}]])) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -216,9 +294,6 @@ ;; Main component ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; This component is being duplicated, when text token row is enabled, -;; this duplication will be removed and we will use token-text-menu*. - (mf/defc text-menu* {::mf/wrap [#(mf/memo' % check-props)]} [{:keys [ids type values applied-tokens]}] @@ -447,8 +522,19 @@ [:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})] [:> text-direction-options* common-props]])])])) -;; This component is being duplicated, when text token row is enabled, -;; this duplication will be removed and we will use this component but renaming it to text-menu*. +;; We are temporarily duplicating some components and modifying the copies +;; to work under a feature flag. When the flag is set to false, the original +;; components are used; when enabled, the new versions are used instead. +;; +;; This approach introduces some code duplication, but it helps avoid +;; scattering conditional (feature flag) logic throughout the codebase, +;; keeping both implementations easier to read and maintain during the transition. +;; +;; Once the feature flag is fully enabled, the old components will be removed +;; and the duplicated code will be cleaned up. +;; +;; Once the feature flag is fully enabled, the old components will be removed +;; and the duplicated code will be cleaned up. (mf/defc token-text-menu* {::mf/wrap [#(mf/memo' % check-props)]} @@ -660,16 +746,15 @@ (when token-dropdown-open? (ts/schedule 0 #(some-> (mf/ref-val dropdown-ref) dom/focus!)))) - [:section {:class (stl/css :element-set) + [:section {:class (stl/css :text-menu) :aria-label (tr "workspace.options.text-options.text-section")} - [:div {:class (stl/css :element-title)} + [:div {:class (stl/css :menu-title)} [:> title-bar* {:collapsable true :collapsed (not main-menu-open?) :on-collapsed toggle-main-menu - :title label - :class (stl/css :title-spacing-text)} + :title label} [:* - (when (and (some? (resolve-delay typography-tokens)) (not typography) ) + (when (and (some? (resolve-delay typography-tokens)) (not typography)) [:> icon-button* {:variant "ghost" :aria-label (tr "ds.inputs.numeric-input.open-token-list-dropdown") :on-click toggle-token-dropdown @@ -691,6 +776,7 @@ :active-tokens (resolve-delay typography-tokens)}] typography + ;; TODO: Review this component and adapt it to new style [:& typography-entry {:file-id typography-file-id :typography typography :local? (= typography-file-id file-id) @@ -698,25 +784,27 @@ :on-change handle-change-typography}] (= typography-id :multiple) - [:div {:class (stl/css :multiple-typography)} - [:span {:class (stl/css :multiple-text)} (tr "workspace.libraries.text.multiple-typography")] + [:div {:class (stl/css :multiple-typography-row)} + [:> text* {:as "span" :typography t/body-small :class (stl/css :multiple-subtitle)} + (tr "workspace.libraries.text.multiple-typography")] [:> icon-button* {:variant "ghost" :aria-label (tr "workspace.libraries.text.multiple-typography-tooltip") :on-click handle-detach-typography + :tooltip-placement "top-left" :icon i/detach}]] :else - [:> text-options #js {:ids ids - :values values - :on-change on-change - :show-recent true - :on-blur - (fn [] - (ts/schedule - 100 + [:> token-text-options* {:ids ids + :values values + :on-change on-change + :show-recent true + :on-blur (fn [] - (when (not= "INPUT" (-> (dom/get-active) dom/get-tag-name)) - (dom/focus! (txu/get-text-editor-content))))))}]) + (ts/schedule + 100 + (fn [] + (when (not= "INPUT" (-> (dom/get-active) dom/get-tag-name)) + (dom/focus! (txu/get-text-editor-content))))))}]) [:div {:class (stl/css :text-align-options)} [:> text-align-options* common-props] @@ -728,10 +816,37 @@ :icon i/menu}]] (when more-options-open? - [:div {:class (stl/css :text-decoration-options)} - [:> vertical-align* common-props] - [:> text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})] - [:> text-direction-options* common-props]])]) + [:div {:class (stl/css :advanced-options)} + [:> text* {:as "span" :typography t/headline-small :class (stl/css :advanced-options-title)} + "Advanced options"] + [:div {:class (stl/css :advanced-option)} + [:> text* {:as "span" :typography t/body-small :class (stl/css :advanced-option-title)} + "Text Case"] + [:> text-transform-options* common-props] + [:> icon-button* {:variant "secondary" + :aria-label "Open token list" + :on-click #() + :icon i/tokens}]] + + [:div {:class (stl/css :advanced-option)} + [:> text* {:as "span" :typography t/body-small :class (stl/css :advanced-option-title)} + "Text Decoration"] + [:> token-text-decoration-options* (mf/spread-props common-props {:token-applied current-token-name})] + [:> icon-button* {:variant "secondary" + :aria-label "Open token list" + :on-click #() + :icon i/tokens}]] + + [:div {:class (stl/css :advanced-option)} + [:> text* {:as "span" :typography t/body-small :class (stl/css :advanced-option-title)} + "Align"] + [:> vertical-align* common-props]] + + + [:div {:class (stl/css :advanced-option)} + [:> text* {:as "span" :typography t/body-small :class (stl/css :advanced-option-title)} + "Direction"] + [:> text-direction-options* common-props]]])]) (when token-dropdown-open? [:> searchable-options-dropdown* {:on-click on-option-click diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.scss index 14c502e3a3..a50c5449d3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.scss @@ -64,3 +64,52 @@ display: flex; gap: var(--sp-xs); } + + +// This rules are used in token-text-menu +.text-menu { + @include sidebar.option-grid-structure; + + position: relative; +} + +.menu-title { + grid-column: span 8; +} + +.multiple-typography-row { + display: flex; + align-items: center; + border-radius: $br-8; + height: $sz-32; + padding: var(--sp-s); + background-color: var(--color-background-tertiary); +} + +.multiple-subtitle { + flex-grow: 1; + color: var(--color-foreground-primary); +} + +.advanced-options { + display: flex; + flex-direction: column; + gap: var(--sp-xs); + padding: var(--sp-m) 0; +} + +.advanced-option { + display: flex; + align-items: center; + gap: var(--sp-xs); +} + +.advanced-options-title { + color: var(--color-foreground-secondary); + padding-block-end: var(--sp-xs); +} + +.advanced-option-title { + color: var(--color-foreground-secondary); + flex-grow: 1; +} \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.cljs index 34f20525c5..3cb58fe957 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.cljs @@ -8,7 +8,6 @@ (:require-macros [app.main.style :as stl]) (:require ["react-virtualized" :as rvt] - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] @@ -461,3 +460,27 @@ [:> spacing-options opts] [:> text-transform-options opts]]])) +;; We are temporarily duplicating some components and modifying the copies +;; to work under a feature flag. When the flag is set to false, the original +;; components are used; when enabled, the new versions are used instead. +;; +;; This approach introduces some code duplication, but it helps avoid +;; scattering conditional (feature flag) logic throughout the codebase, +;; keeping both implementations easier to read and maintain during the transition. + +(mf/defc token-text-options* + [{:keys [ids editor values on-change on-blur show-recent]}] + (let [full-size-selector? (and show-recent (= (mf/use-ctx ctx/sidebar) :right)) + opts #js {:editor editor + :ids ids + :values values + :on-change on-change + :on-blur on-blur + :show-recent show-recent + :full-size-selector full-size-selector?}] + [:div {:class (stl/css-case :text-options true + :text-options-full-size full-size-selector?)} + [:> font-options opts] + [:div {:class (stl/css :typography-variations)} + [:> spacing-options opts]]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index f99364cbc9..d51e134911 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.types.shape.layout :as ctl] + [app.config :as cf] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] @@ -32,10 +33,11 @@ {::mf/wrap [mf/memo]} [{:keys [shape shapes-with-children libraries file-id page-id]}] - (let [id (dm/get-prop shape :id) - type (dm/get-prop shape :type) - ids (mf/with-memo [id] [id]) - shapes (mf/with-memo [shape] [shape]) + (let [id (dm/get-prop shape :id) + type (dm/get-prop shape :type) + ids (mf/with-memo [id] [id]) + shapes (mf/with-memo [shape] [shape]) + token-row (contains? cf/flags :token-typography-row) applied-tokens (get shape :applied-tokens) @@ -171,10 +173,29 @@ [:& blur-menu {:type type :ids blur-ids :values blur-values}]) (when-not (empty? text-ids) - [:> ot/text-menu* {:type type - :ids text-ids - :values text-values - :applied-tokens text-tokens}]) + ;; We are temporarily duplicating some components and modifying the copies + ;; to work under a feature flag. When the flag is set to false, the original + ;; components are used; when enabled, the new versions are used instead. + ;; + ;; This approach introduces some code duplication, but it helps avoid + ;; scattering conditional (feature flag) logic throughout the codebase, + ;; keeping both implementations easier to read and maintain during the transition. + ;; + ;; Once the feature flag is fully enabled, the old components will be removed + ;; and the duplicated code will be cleaned up. + + (if token-row + [:> ot/token-text-menu* + {:ids text-ids + :type type + :applied-tokens text-tokens + :values text-values}] + + [:> ot/text-menu* + {:ids text-ids + :type type + :applied-tokens text-tokens + :values text-values}])) (when-not (empty? svg-values) [:& svg-attrs-menu {:ids ids :values svg-values}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 45f8b994e4..a4aa728358 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -19,6 +19,7 @@ [app.common.types.text :as txt] [app.common.types.token :as tt] [app.common.weak :as weak] + [app.config :as cf] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]] @@ -326,7 +327,8 @@ (mf/defc options* {::mf/wrap [#(mf/memo' % check-options-props)]} [{:keys [shapes shapes-with-children page-id file-id libraries] :as props}] - (let [shape-ids + (let [token-row (contains? cf/flags :token-typography-row) + shape-ids (mf/with-memo [shapes] (into #{} d/xf:map-id shapes)) @@ -480,12 +482,29 @@ (when-not (or (empty? constraint-ids) ^boolean is-layout-child?) [:& constraints-menu {:ids constraint-ids :values constraint-values}]) + ;; We are temporarily duplicating some components and modifying the copies + ;; to work under a feature flag. When the flag is set to false, the original + ;; components are used; when enabled, the new versions are used instead. + ;; + ;; This approach introduces some code duplication, but it helps avoid + ;; scattering conditional (feature flag) logic throughout the codebase, + ;; keeping both implementations easier to read and maintain during the transition. + ;; + ;; Once the feature flag is fully enabled, the old components will be removed + ;; and the duplicated code will be cleaned up. (when-not (empty? text-ids) - [:> ot/text-menu* - {:type type - :ids text-ids - :values text-values - :applied-tokens text-tokens}]) + (if token-row + [:> ot/token-text-menu* + {:ids text-ids + :type type + :applied-tokens text-tokens + :values text-values}] + + [:> ot/text-menu* + {:ids text-ids + :type type + :applied-tokens text-tokens + :values text-values}])) (when-not (empty? fill-ids) [:> fill/fill-menu* {:type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 80f9b742a7..6b091842ac 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -179,7 +179,6 @@ :values (select-keys shape constraint-attrs)}]) (if token-row - [:> token-text-menu* {:ids ids :type type