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 118a1f5a25..9a7fd41433 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 @@ -28,8 +28,9 @@ [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.hooks :as hooks] + [app.main.ui.workspace.sidebar.options.menus.text-shared :refer [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 [text-options typography-entry]] + [app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry]] [app.main.ui.workspace.tokens.management.forms.controls.utils :as csu] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] 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 new file mode 100644 index 0000000000..34f20525c5 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.cljs @@ -0,0 +1,463 @@ +;; 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.sidebar.options.menus.text-shared + (: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] + [app.common.types.text :as txt] + [app.main.data.fonts :as fts] + [app.main.data.shortcuts :as dsc] + [app.main.features :as features] + [app.main.fonts :as fonts] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.components.editable-select :refer [editable-select]] + [app.main.ui.components.numeric-input :refer [numeric-input*]] + [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] + [app.main.ui.components.search-bar :refer [search-bar*]] + [app.main.ui.components.select :refer [select]] + [app.main.ui.context :as ctx] + [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.icons :as deprecated-icon] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [app.util.keyboard :as kbd] + [app.util.strings :as ust] + [app.util.timers :as tm] + [cuerdas.core :as str] + [goog.events :as events] + [rumext.v2 :as mf])) + +(defn- attr->string [value] + (if (= value :multiple) + "" + (ust/format-precision value 2))) + +(defn- get-next-font + [{:keys [id] :as current} fonts] + (if (seq fonts) + (let [index (d/index-of-pred fonts #(= (:id %) id)) + index (or index -1) + next (ex/ignoring (nth fonts (inc index)))] + (or next (first fonts))) + current)) + +(defn- get-prev-font + [{:keys [id] :as current} fonts] + (if (seq fonts) + (let [index (d/index-of-pred fonts #(= (:id %) id)) + next (ex/ignoring (nth fonts (dec index)))] + (or next (peek fonts))) + current)) + +(mf/defc font-item* + {::mf/wrap [mf/memo]} + [{:keys [font is-current on-click style]}] + (let [item-ref (mf/use-ref) + on-click (mf/use-fn (mf/deps font) #(on-click font))] + + (mf/use-effect + (mf/deps is-current) + (fn [] + (when is-current + (let [element (mf/ref-val item-ref)] + (when-not (dom/is-in-viewport? element) + (dom/scroll-into-view! element)))))) + + [:div {:class (stl/css :font-wrapper) + :style style + :ref item-ref + :on-click on-click} + [:div {:class (stl/css-case :font-item true + :selected is-current)} + [:span {:class (stl/css :label)} (:name font)] + [:span {:class (stl/css :icon)} (when is-current deprecated-icon/tick)]]])) + +(declare row-renderer) + +(defn filter-fonts + [{:keys [term backends]} fonts] + (let [term (str/lower term) + xform (cond-> (map identity) + (seq term) + (comp (filter #(str/includes? (str/lower (:name %)) term))) + + (seq backends) + (comp (filter #(contains? backends (:backend %)))))] + (into [] xform fonts))) + +(mf/defc font-selector* + [{:keys [on-select on-close current-font show-recent full-size]}] + (let [selected (mf/use-state current-font) + state* (mf/use-state + #(do {:term "" :backends #{}})) + state (deref state*) + + flist (mf/use-ref) + input (mf/use-ref) + + fonts (mf/deref fonts/fonts) + fonts (mf/with-memo [state fonts] + (filter-fonts state fonts)) + + recent-fonts (mf/deref refs/recent-fonts) + recent-fonts (mf/with-memo [state recent-fonts] + (filter-fonts state recent-fonts)) + + + full-size? (boolean (and full-size show-recent)) + + select-next + (mf/use-fn + (mf/deps fonts) + (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (swap! selected get-next-font fonts))) + + select-prev + (mf/use-fn + (mf/deps fonts) + (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (swap! selected get-prev-font fonts))) + + on-key-down + (mf/use-fn + (mf/deps fonts) + (fn [event] + (cond + (kbd/up-arrow? event) (select-prev event) + (kbd/down-arrow? event) (select-next event) + (kbd/esc? event) (on-close) + (kbd/enter? event) (on-close) + :else (dom/focus! (mf/ref-val input))))) + + on-filter-change + (mf/use-fn + (fn [event] + (swap! state* assoc :term event))) + + on-select-and-close + (mf/use-fn + (mf/deps on-select on-close) + (fn [font] + (on-select font) + (on-close)))] + + (mf/with-effect [fonts] + (let [key (events/listen js/document "keydown" on-key-down)] + #(events/unlistenByKey key))) + + (mf/with-effect [@selected] + (when-let [inst (mf/ref-val flist)] + (when-let [index (:index @selected)] + (.scrollToRow ^js inst index)))) + + (mf/with-effect [@selected] + (on-select @selected)) + + (mf/with-effect [] + (st/emit! (dsc/push-shortcuts :typography {})) + (fn [] + (st/emit! (dsc/pop-shortcuts :typography)))) + + (mf/with-effect [] + (let [index (d/index-of-pred fonts #(= (:id %) (:id current-font))) + inst (mf/ref-val flist)] + (tm/schedule + #(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})] + (.scrollToPosition ^js inst offset))))) + + [:div {:class (stl/css :font-selector)} + [:div {:class (stl/css-case :font-selector-dropdown true :font-selector-dropdown-full-size full-size?)} + [:div {:class (stl/css :header)} + [:> search-bar* {:on-change on-filter-change + :value (:term state) + :auto-focus true + :placeholder (tr "workspace.options.search-font")}] + (when (and recent-fonts show-recent) + [:section {:class (stl/css :show-recent)} + [:p {:class (stl/css :title)} (tr "workspace.options.recent-fonts")] + (for [[idx font] (d/enumerate recent-fonts)] + [:> font-item* {:key (dm/str "font-" idx) + :font font + :style {} + :on-click on-select-and-close + :is-current (= (:id font) (:id @selected))}])])] + + [:div {:class (stl/css-case :fonts-list true + :fonts-list-full-size full-size?)} + [:> rvt/AutoSizer {} + (fn [props] + (let [width (unchecked-get props "width") + height (unchecked-get props "height") + render #(row-renderer fonts @selected on-select-and-close %)] + (mf/html + [:> rvt/List #js {:height height + :ref flist + :width width + :rowCount (count fonts) + :rowHeight 36 + :rowRenderer render}])))]]]])) + +(defn row-renderer + [fonts selected on-select props] + (let [index (unchecked-get props "index") + key (unchecked-get props "key") + style (unchecked-get props "style") + font (nth fonts index)] + (mf/html + [:> font-item* {:key key + :font font + :style style + :on-click on-select + :is-current (= (:id font) (:id selected))}]))) + +(mf/defc font-options + {::mf/wrap-props false} + [{:keys [values on-change on-blur show-recent full-size-selector]}] + (let [{:keys [font-id font-size font-variant-id]} values + + font-id (or font-id (:font-id txt/default-typography)) + font-size (or font-size (:font-size txt/default-typography)) + font-variant-id (or font-variant-id (:font-variant-id txt/default-typography)) + + fonts (mf/deref fonts/fontsdb) + font (get fonts font-id) + + last-font (mf/use-ref nil) + + open-selector? (mf/use-state false) + + change-font + (mf/use-fn + (mf/deps on-change fonts) + (fn [new-font-id] + (let [{:keys [family] :as font} (get fonts new-font-id) + {:keys [id name weight style]} (fonts/get-default-variant font)] + (on-change {:font-id new-font-id + :font-family family + :font-variant-id (or id name) + :font-weight weight + :font-style style}) + (mf/set-ref-val! last-font font)))) + + on-font-size-change + (mf/use-fn + (mf/deps on-change) + (fn [new-font-size] + (when-not (str/empty? new-font-size) + (on-change {:font-size (str new-font-size)})))) + + on-font-variant-change + (mf/use-fn + (mf/deps font on-change) + (fn [new-variant-id] + (let [variant (d/seek #(= new-variant-id (:id %)) (:variants font))] + (when-not (nil? variant) + (on-change {:font-id (:id font) + :font-family (:family font) + :font-variant-id new-variant-id + :font-weight (:weight variant) + :font-style (:style variant)})) + ;; NOTE: the select component we are using does not fire on-blur event + ;; so we need to call on-blur manually + (when (some? on-blur) + (on-blur))))) + + on-font-select + (mf/use-fn + (mf/deps change-font) + (fn [font*] + (when (not= font font*) + (change-font (:id font*))) + + (when (some? on-blur) + (on-blur)))) + + on-font-selector-close + (mf/use-fn + (fn [] + (reset! open-selector? false) + (when (some? on-blur) + (on-blur)) + (when (mf/ref-val last-font) + (st/emit! (fts/add-recent-font (mf/ref-val last-font))))))] + + [:* + (when @open-selector? + [:> font-selector* + {:current-font font + :on-close on-font-selector-close + :on-select on-font-select + :full-size full-size-selector + :show-recent show-recent}]) + + [:div {:class (stl/css :font-option) + :title (tr "inspect.attributes.typography.font-family") + :on-click #(reset! open-selector? true)} + (cond + (or (= :multiple font-id) (= "mixed" font-id)) + "--" + + (some? font) + [:* + [:span {:class (stl/css :name)} + (:name font)] + [:span {:class (stl/css :icon)} + deprecated-icon/arrow]] + + :else + (tr "dashboard.fonts.deleted-placeholder"))] + + [:div {:class (stl/css :font-modifiers)} + [:div {:class (stl/css :font-size-options) + :title (tr "inspect.attributes.typography.font-size")} + (let [size-options [8 9 10 11 12 14 16 18 24 36 48 72] + size-options (if (= font-size :multiple) (into [""] size-options) size-options)] + [:& editable-select + {:value (if (= font-size :multiple) :multiple (attr->string font-size)) + :class (stl/css :font-size-select) + :aria-label (tr "inspect.attributes.typography.font-size") + :input-class (stl/css :numeric-input) + :options size-options + :type "number" + :placeholder (tr "settings.multiple") + :min 3 + :max 1000 + :on-change on-font-size-change + :on-blur on-blur}])] + + [:div {:class (stl/css :font-variant-options) + :title (tr "inspect.attributes.typography.font-style")} + (let [basic-variant-options (->> (:variants font) + (map (fn [variant] + {:value (:id variant) + :key (pr-str variant) + :label (:name variant)}))) + variant-options (if (or (= font-variant-id :multiple) (= font-variant-id "mixed")) + (conj basic-variant-options + {:value "" + :key :multiple-variants + :label "--"}) + basic-variant-options) + font-variant-value (attr->string font-variant-id) + font-variant-value (if (= font-variant-value "mixed") "" font-variant-value)] + + ;; TODO Add disabled mode + [:& select + {:class (stl/css :font-variant-select) + :default-value font-variant-value + :options variant-options + :on-change on-font-variant-change + :on-blur on-blur}])]]])) + +(mf/defc spacing-options + {::mf/wrap-props false} + [{:keys [values on-change on-blur]}] + (let [{:keys [line-height + letter-spacing]} values + line-height (or line-height "1.2") + letter-spacing (or letter-spacing "0") + handle-change + (fn [value attr] + (on-change {attr (str value)}))] + + [:div {:class (stl/css :spacing-options)} + [:div {:class (stl/css :line-height) + :title (tr "inspect.attributes.typography.line-height")} + [:span {:class (stl/css :icon) + :alt (tr "workspace.options.text-options.line-height")} + deprecated-icon/text-lineheight] + [:> numeric-input* + {:min -200 + :max 200 + :step 0.1 + :default-value "1.2" + :class (stl/css :line-height-input) + :aria-label (tr "inspect.attributes.typography.line-height") + :value (attr->string line-height) + :placeholder (if (= :multiple line-height) (tr "settings.multiple") "--") + :nillable (= :multiple line-height) + :on-change #(handle-change % :line-height) + :on-blur on-blur}]] + + [:div {:class (stl/css :letter-spacing) + :title (tr "inspect.attributes.typography.letter-spacing")} + [:span + {:class (stl/css :icon) + :alt (tr "workspace.options.text-options.letter-spacing")} + deprecated-icon/text-letterspacing] + [:> numeric-input* + {:min -200 + :max 200 + :step 0.1 + :default-value "0" + :class (stl/css :letter-spacing-input) + :aria-label (tr "inspect.attributes.typography.letter-spacing") + :value (attr->string letter-spacing) + :placeholder (if (= :multiple letter-spacing) (tr "settings.multiple") "--") + :on-change #(handle-change % :letter-spacing) + :nillable (= :multiple letter-spacing) + :on-blur on-blur}]]])) + +(mf/defc text-transform-options + {::mf/wrap-props false} + [{:keys [values on-change on-blur]}] + (let [text-transform (or (:text-transform values) "none") + unset-value (if (features/active-feature? @st/state "text-editor/v2") "none" "unset") + handle-change + (fn [type] + (if (= text-transform type) + (on-change {:text-transform unset-value}) + (on-change {:text-transform type})) + (when (some? on-blur) (on-blur)))] + + [:div {:class (stl/css :text-transform)} + [:& radio-buttons {:selected text-transform + :on-change handle-change + :name "text-transform"} + [:& radio-button {:icon i/text-uppercase + :type "checkbox" + :title (tr "inspect.attributes.typography.text-transform.uppercase") + :value "uppercase" + :id "text-transform-uppercase"}] + [:& radio-button {:icon i/text-mixed + :type "checkbox" + :value "capitalize" + :title (tr "inspect.attributes.typography.text-transform.capitalize") + :id "text-transform-capitalize"}] + [:& radio-button {:icon i/text-lowercase + :type "checkbox" + :title (tr "inspect.attributes.typography.text-transform.lowercase") + :value "lowercase" + :id "text-transform-lowercase"}]]])) + +(mf/defc text-options + {::mf/wrap-props false} + [{: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] + [:> text-transform-options opts]]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.scss new file mode 100644 index 0000000000..a9f91e206a --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_shared.scss @@ -0,0 +1,546 @@ +// 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 "refactor/common-refactor.scss" as deprecated; + +.typography-entry { + display: flex; + flex-direction: row; + align-items: center; + height: deprecated.$s-32; + width: 100%; + border-radius: deprecated.$br-8; + background-color: var(--assets-item-background-color); + color: var(--assets-item-name-foreground-color-hover); + + &:hover, + &:focus-within { + background-color: var(--assets-item-background-color-hover); + color: var(--assets-item-name-foreground-color-hover); + } + + &.selected { + border: deprecated.$s-1 solid var(--assets-item-border-color); + } + + .element-set-actions { + display: flex; + visibility: hidden; + + .element-set-actions-button, + .menu-btn { + @extend %button-tertiary; + + height: deprecated.$s-32; + width: deprecated.$s-28; + + svg { + @extend %button-icon; + } + + &:active { + background-color: transparent; + } + } + } + + &:hover { + background-color: var(--assets-item-background-color-hover); + + .element-set-actions { + visibility: visible; + } + } +} + +.typography-selection-wrapper { + display: grid; + grid-template-columns: deprecated.$s-24 auto 1fr; + flex: 1; + height: 100%; + width: 100%; + padding: 0 deprecated.$s-12; + + &.is-selectable { + cursor: pointer; + } +} + +.typography-sample { + @include deprecated.flexCenter; + + min-width: deprecated.$s-24; + height: deprecated.$s-32; + color: var(--assets-item-name-foreground-color); +} + +.typography-name, +.typography-font { + @include deprecated.bodySmallTypography; + @include deprecated.textEllipsis; + + display: block; + align-self: center; + margin-left: deprecated.$s-6; +} + +.typography-name { + color: var(--assets-item-name-foreground-color); +} + +.typography-font { + color: var(--assets-item-name-foreground-color-rest); +} + +.font-name-wrapper { + @include deprecated.bodySmallTypography; + + display: flex; + align-items: center; + height: deprecated.$s-32; + width: 100%; + border-radius: deprecated.$br-8; + border: deprecated.$s-1 solid transparent; + box-sizing: border-box; + background-color: var(--assets-item-background-color); + margin-bottom: deprecated.$s-4; + padding: deprecated.$s-8 deprecated.$s-0 deprecated.$s-8 deprecated.$s-12; + + .typography-sample-input { + @include deprecated.flexCenter; + + width: deprecated.$s-24; + height: 100%; + font-size: deprecated.$fs-16; + color: var(--assets-item-name-foreground-color-hover); + } + + .adv-typography-name { + @include deprecated.removeInputStyle; + + font-size: deprecated.$fs-12; + color: var(--input-foreground-color-active); + flex-grow: 1; + padding-left: deprecated.$s-6; + margin: 0; + } + + .action-btn { + @extend %button-tertiary; + @include deprecated.flexCenter; + + width: deprecated.$s-28; + height: deprecated.$s-28; + + svg { + @extend %button-icon-small; + + stroke: var(--icon-foreground); + } + + &:active { + background-color: transparent; + } + } + + &:focus-within { + border: deprecated.$s-1 solid var(--input-border-color-active); + + .adv-typography-name { + color: var(--input-foreground-color-active); + } + } + + &:hover { + background-color: var(--assets-item-background-color-hover); + } +} + +.advanced-options-wrapper { + height: 100%; + width: 100%; + background-color: var(--assets-title-background-color); +} + +.typography-info-wrapper { + @include deprecated.flexColumn; + + margin-bottom: deprecated.$s-12; + + .typography-name-wrapper { + @extend %asset-element; + + display: grid; + grid-template-columns: deprecated.$s-24 auto 1fr deprecated.$s-28; + flex: 1; + height: deprecated.$s-32; + width: 100%; + padding: 0 0 0 deprecated.$s-12; + background-color: var(--assets-item-background-color-hover); + margin-bottom: deprecated.$s-4; + + .typography-sample { + @include deprecated.flexCenter; + + min-width: deprecated.$s-24; + font-size: deprecated.$fs-16; + height: deprecated.$s-32; + padding: 0; + color: var(--assets-item-name-foreground-color-hover); + } + + .typography-name { + @include deprecated.bodySmallTypography; + @include deprecated.textEllipsis; + + display: flex; + align-items: center; + justify-content: flex-start; + margin-left: deprecated.$s-6; + color: var(--assets-item-name-foreground-color-hover); + } + + .typography-font { + @include deprecated.bodySmallTypography; + @include deprecated.textEllipsis; + + margin-left: deprecated.$s-6; + display: flex; + align-items: center; + justify-content: flex-start; + min-width: 0; + color: var(--assets-item-name-foreground-color); + } + + .action-btn { + @extend %button-tertiary; + + width: deprecated.$s-28; + height: deprecated.$s-32; + + svg { + @extend %button-icon; + } + + &:active { + background-color: transparent; + } + } + } + + .info-row { + display: grid; + grid-template-columns: 50% 50%; + height: deprecated.$s-32; + + --calculated-width: calc(var(--right-sidebar-width) - deprecated.$s-48); + + padding-left: deprecated.$s-2; + + .info-label { + @include deprecated.bodySmallTypography; + @include deprecated.textEllipsis; + + width: calc(var(--calculated-width) / 2); + padding-top: deprecated.$s-8; + color: var(--assets-item-name-foreground-color); + } + + .info-content { + @include deprecated.bodySmallTypography; + @include deprecated.textEllipsis; + + padding-top: deprecated.$s-8; + width: calc(var(--calculated-width) / 2); + color: var(--assets-item-name-foreground-color-hover); + } + } + + .link-btn { + @include deprecated.uppercaseTitleTipography; + @extend %button-secondary; + + width: 100%; + height: deprecated.$s-32; + border-radius: deprecated.$br-8; + + &:hover { + background-color: var(--button-secondary-background-color-hover); + color: var(--button-secondary-foreground-color-hover); + border: deprecated.$s-1 solid var(--button-secondary-border-color-hover); + text-decoration: none; + + svg { + stroke: var(--button-secondary-foreground-color-hover); + } + } + + &:focus { + background-color: var(--button-secondary-background-color-focus); + color: var(--button-secondary-foreground-color-focus); + border: deprecated.$s-1 solid var(--button-secondary-border-color-focus); + + svg { + stroke: var(--button-secondary-foreground-color-focus); + } + } + } +} + +.text-options { + @include deprecated.flexColumn; + + max-width: var(--options-width); + + &:not(.text-options-full-size) { + position: relative; + } + + .font-option { + @include deprecated.bodySmallTypography; + @extend %asset-element; + + padding: deprecated.$s-8 0 deprecated.$s-8 deprecated.$s-8; + cursor: pointer; + + .name { + flex-grow: 1; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } + + .icon { + @include deprecated.flexCenter; + + height: deprecated.$s-28; + width: deprecated.$s-28; + + svg { + @extend %button-icon-small; + + stroke: var(--icon-foreground); + transform: rotate(90deg); + } + } + } + + .font-modifiers { + display: flex; + gap: deprecated.$s-4; + + .font-size-options { + @extend %asset-element; + @include deprecated.bodySmallTypography; + + flex-grow: 1; + width: deprecated.$s-60; + margin: 0; + padding: 0; + border: deprecated.$s-1 solid var(--input-border-color); + position: relative; + + .icon { + @include deprecated.flexCenter; + + height: deprecated.$s-28; + min-width: deprecated.$s-28; + + svg { + @extend %button-icon-small; + + stroke: var(--icon-foreground); + transform: rotate(90deg); + } + } + } + + .font-variant-options { + padding: 0; + flex-grow: 2; + } + } + + .typography-variations { + @include deprecated.flexRow; + + .spacing-options { + @include deprecated.flexRow; + + .line-height, + .letter-spacing { + @extend %input-element; + @include deprecated.bodySmallTypography; + + .icon { + @include deprecated.flexCenter; + + width: deprecated.$s-28; + + svg { + @extend %button-icon-small; + } + } + } + } + + .text-transform { + @extend %asset-element; + + width: fit-content; + padding: 0; + background-color: var(--radio-btns-background-color); + + &:hover { + background-color: var(--radio-btns-background-color); + } + } + } +} + +.font-size-select { + @include deprecated.removeInputStyle; + @include deprecated.bodySmallTypography; + + height: deprecated.$s-32; + height: 100%; + width: 100%; + margin: 0; + padding: deprecated.$s-8; + + .numeric-input { + @extend %input-base; + @include deprecated.bodySmallTypography; + + padding: 0; + } +} + +.font-selector { + @include deprecated.flexCenter; + + position: absolute; + top: 0; + left: 0; + right: 0; + height: 100%; + width: 100%; + z-index: deprecated.$z-index-4; +} + +.show-recent { + border-radius: deprecated.$br-8 deprecated.$br-8 0 0; + background: var(--dropdown-background-color); + border: deprecated.$s-1 solid var(--color-background-quaternary); + border-block-end: none; +} + +.font-selector-dropdown { + width: 100%; + + &:not(.font-selector-dropdown-full-size) { + display: flex; + flex-direction: column; + flex-grow: 1; + height: 100%; + } + + .header { + display: grid; + row-gap: deprecated.$s-2; + + .title { + @include deprecated.uppercaseTitleTipography; + + color: var(--title-foreground-color); + margin: 0; + padding: deprecated.$s-12; + } + } +} + +.font-wrapper { + padding-bottom: deprecated.$s-4; + cursor: pointer; +} + +.font-item { + @extend %asset-element; + + margin-bottom: deprecated.$s-4; + border-radius: deprecated.$br-8; + display: flex; + + .icon { + @include deprecated.flexCenter; + + height: deprecated.$s-28; + width: deprecated.$s-28; + + svg { + @extend %button-icon-small; + + stroke: var(--icon-foreground); + } + } + + &.selected { + color: var(--assets-item-name-foreground-color-hover); + + .icon { + svg { + stroke: var(--assets-item-name-foreground-color-hover); + } + } + } + + .label { + @include deprecated.bodySmallTypography; + @include deprecated.textEllipsis; + + flex-grow: 1; + min-width: 0; + } +} + +.font-selector-dropdown-full-size { + height: calc(100vh - 48px); // TODO: ugly hack :( Find a workaround for this. + display: grid; + grid-template-rows: auto 1fr; + padding: deprecated.$s-2 deprecated.$s-12 deprecated.$s-12 deprecated.$s-12; +} + +.fonts-list { + position: relative; + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 100%; + width: 100%; + height: 100%; + padding: deprecated.$s-2; + border-radius: deprecated.$br-8; + background-color: var(--dropdown-background-color); + overflow: hidden; + + &:not(.fonts-list-full-size) { + margin-block-start: deprecated.$s-2; + } +} + +.fonts-list-full-size { + border-start-start-radius: 0; + border-start-end-radius: 0; + border: deprecated.$s-1 solid var(--color-background-quaternary); + + // TODO: this should belong to typography-entry , but atm we don't have a clear + // way of accessing whether we are in fullsize mode or not + .selected { + padding-inline-end: 0; + } +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index 90124b22aa..0ec9d83374 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -7,464 +7,24 @@ (ns app.main.ui.workspace.sidebar.options.menus.typography (: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] - [app.common.types.text :as txt] [app.main.constants :refer [max-input-length]] [app.main.data.common :as dcm] - [app.main.data.fonts :as fts] - [app.main.data.shortcuts :as dsc] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.undo :as dwu] - [app.main.features :as features] [app.main.fonts :as fonts] - [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.editable-select :refer [editable-select]] - [app.main.ui.components.numeric-input :refer [numeric-input*]] - [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] - [app.main.ui.components.search-bar :refer [search-bar*]] - [app.main.ui.components.select :refer [select]] [app.main.ui.context :as ctx] [app.main.ui.ds.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.sidebar.options.menus.text-shared :refer [text-options]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] - [app.util.strings :as ust] [app.util.timers :as tm] [cuerdas.core :as str] - [goog.events :as events] [rumext.v2 :as mf])) -(defn- attr->string [value] - (if (= value :multiple) - "" - (ust/format-precision value 2))) - -(defn- get-next-font - [{:keys [id] :as current} fonts] - (if (seq fonts) - (let [index (d/index-of-pred fonts #(= (:id %) id)) - index (or index -1) - next (ex/ignoring (nth fonts (inc index)))] - (or next (first fonts))) - current)) - -(defn- get-prev-font - [{:keys [id] :as current} fonts] - (if (seq fonts) - (let [index (d/index-of-pred fonts #(= (:id %) id)) - next (ex/ignoring (nth fonts (dec index)))] - (or next (peek fonts))) - current)) - -(mf/defc font-item* - {::mf/wrap [mf/memo]} - [{:keys [font is-current on-click style]}] - (let [item-ref (mf/use-ref) - on-click (mf/use-fn (mf/deps font) #(on-click font))] - - (mf/use-effect - (mf/deps is-current) - (fn [] - (when is-current - (let [element (mf/ref-val item-ref)] - (when-not (dom/is-in-viewport? element) - (dom/scroll-into-view! element)))))) - - [:div {:class (stl/css :font-wrapper) - :style style - :ref item-ref - :on-click on-click} - [:div {:class (stl/css-case :font-item true - :selected is-current)} - [:span {:class (stl/css :label)} (:name font)] - [:span {:class (stl/css :icon)} (when is-current deprecated-icon/tick)]]])) - -(declare row-renderer) - -(defn filter-fonts - [{:keys [term backends]} fonts] - (let [term (str/lower term) - xform (cond-> (map identity) - (seq term) - (comp (filter #(str/includes? (str/lower (:name %)) term))) - - (seq backends) - (comp (filter #(contains? backends (:backend %)))))] - (into [] xform fonts))) - -(mf/defc font-selector* - [{:keys [on-select on-close current-font show-recent full-size]}] - (let [selected (mf/use-state current-font) - state* (mf/use-state - #(do {:term "" :backends #{}})) - state (deref state*) - - flist (mf/use-ref) - input (mf/use-ref) - - fonts (mf/deref fonts/fonts) - fonts (mf/with-memo [state fonts] - (filter-fonts state fonts)) - - recent-fonts (mf/deref refs/recent-fonts) - recent-fonts (mf/with-memo [state recent-fonts] - (filter-fonts state recent-fonts)) - - - full-size? (boolean (and full-size show-recent)) - - select-next - (mf/use-fn - (mf/deps fonts) - (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (swap! selected get-next-font fonts))) - - select-prev - (mf/use-fn - (mf/deps fonts) - (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) - (swap! selected get-prev-font fonts))) - - on-key-down - (mf/use-fn - (mf/deps fonts) - (fn [event] - (cond - (kbd/up-arrow? event) (select-prev event) - (kbd/down-arrow? event) (select-next event) - (kbd/esc? event) (on-close) - (kbd/enter? event) (on-close) - :else (dom/focus! (mf/ref-val input))))) - - on-filter-change - (mf/use-fn - (fn [event] - (swap! state* assoc :term event))) - - on-select-and-close - (mf/use-fn - (mf/deps on-select on-close) - (fn [font] - (on-select font) - (on-close)))] - - (mf/with-effect [fonts] - (let [key (events/listen js/document "keydown" on-key-down)] - #(events/unlistenByKey key))) - - (mf/with-effect [@selected] - (when-let [inst (mf/ref-val flist)] - (when-let [index (:index @selected)] - (.scrollToRow ^js inst index)))) - - (mf/with-effect [@selected] - (on-select @selected)) - - (mf/with-effect [] - (st/emit! (dsc/push-shortcuts :typography {})) - (fn [] - (st/emit! (dsc/pop-shortcuts :typography)))) - - (mf/with-effect [] - (let [index (d/index-of-pred fonts #(= (:id %) (:id current-font))) - inst (mf/ref-val flist)] - (tm/schedule - #(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})] - (.scrollToPosition ^js inst offset))))) - - [:div {:class (stl/css :font-selector)} - [:div {:class (stl/css-case :font-selector-dropdown true :font-selector-dropdown-full-size full-size?)} - [:div {:class (stl/css :header)} - [:> search-bar* {:on-change on-filter-change - :value (:term state) - :auto-focus true - :placeholder (tr "workspace.options.search-font")}] - (when (and recent-fonts show-recent) - [:section {:class (stl/css :show-recent)} - [:p {:class (stl/css :title)} (tr "workspace.options.recent-fonts")] - (for [[idx font] (d/enumerate recent-fonts)] - [:> font-item* {:key (dm/str "font-" idx) - :font font - :style {} - :on-click on-select-and-close - :is-current (= (:id font) (:id @selected))}])])] - - [:div {:class (stl/css-case :fonts-list true - :fonts-list-full-size full-size?)} - [:> rvt/AutoSizer {} - (fn [props] - (let [width (unchecked-get props "width") - height (unchecked-get props "height") - render #(row-renderer fonts @selected on-select-and-close %)] - (mf/html - [:> rvt/List #js {:height height - :ref flist - :width width - :rowCount (count fonts) - :rowHeight 36 - :rowRenderer render}])))]]]])) - -(defn row-renderer - [fonts selected on-select props] - (let [index (unchecked-get props "index") - key (unchecked-get props "key") - style (unchecked-get props "style") - font (nth fonts index)] - (mf/html - [:> font-item* {:key key - :font font - :style style - :on-click on-select - :is-current (= (:id font) (:id selected))}]))) - -(mf/defc font-options - {::mf/wrap-props false} - [{:keys [values on-change on-blur show-recent full-size-selector]}] - (let [{:keys [font-id font-size font-variant-id]} values - - font-id (or font-id (:font-id txt/default-typography)) - font-size (or font-size (:font-size txt/default-typography)) - font-variant-id (or font-variant-id (:font-variant-id txt/default-typography)) - - fonts (mf/deref fonts/fontsdb) - font (get fonts font-id) - - last-font (mf/use-ref nil) - - open-selector? (mf/use-state false) - - change-font - (mf/use-fn - (mf/deps on-change fonts) - (fn [new-font-id] - (let [{:keys [family] :as font} (get fonts new-font-id) - {:keys [id name weight style]} (fonts/get-default-variant font)] - (on-change {:font-id new-font-id - :font-family family - :font-variant-id (or id name) - :font-weight weight - :font-style style}) - (mf/set-ref-val! last-font font)))) - - on-font-size-change - (mf/use-fn - (mf/deps on-change) - (fn [new-font-size] - (when-not (str/empty? new-font-size) - (on-change {:font-size (str new-font-size)})))) - - on-font-variant-change - (mf/use-fn - (mf/deps font on-change) - (fn [new-variant-id] - (let [variant (d/seek #(= new-variant-id (:id %)) (:variants font))] - (when-not (nil? variant) - (on-change {:font-id (:id font) - :font-family (:family font) - :font-variant-id new-variant-id - :font-weight (:weight variant) - :font-style (:style variant)})) - ;; NOTE: the select component we are using does not fire on-blur event - ;; so we need to call on-blur manually - (when (some? on-blur) - (on-blur))))) - - on-font-select - (mf/use-fn - (mf/deps change-font) - (fn [font*] - (when (not= font font*) - (change-font (:id font*))) - - (when (some? on-blur) - (on-blur)))) - - on-font-selector-close - (mf/use-fn - (fn [] - (reset! open-selector? false) - (when (some? on-blur) - (on-blur)) - (when (mf/ref-val last-font) - (st/emit! (fts/add-recent-font (mf/ref-val last-font))))))] - - [:* - (when @open-selector? - [:> font-selector* - {:current-font font - :on-close on-font-selector-close - :on-select on-font-select - :full-size full-size-selector - :show-recent show-recent}]) - - [:div {:class (stl/css :font-option) - :title (tr "inspect.attributes.typography.font-family") - :on-click #(reset! open-selector? true)} - (cond - (or (= :multiple font-id) (= "mixed" font-id)) - "--" - - (some? font) - [:* - [:span {:class (stl/css :name)} - (:name font)] - [:span {:class (stl/css :icon)} - deprecated-icon/arrow]] - - :else - (tr "dashboard.fonts.deleted-placeholder"))] - - [:div {:class (stl/css :font-modifiers)} - [:div {:class (stl/css :font-size-options) - :title (tr "inspect.attributes.typography.font-size")} - (let [size-options [8 9 10 11 12 14 16 18 24 36 48 72] - size-options (if (= font-size :multiple) (into [""] size-options) size-options)] - [:& editable-select - {:value (if (= font-size :multiple) :multiple (attr->string font-size)) - :class (stl/css :font-size-select) - :aria-label (tr "inspect.attributes.typography.font-size") - :input-class (stl/css :numeric-input) - :options size-options - :type "number" - :placeholder (tr "settings.multiple") - :min 3 - :max 1000 - :on-change on-font-size-change - :on-blur on-blur}])] - - [:div {:class (stl/css :font-variant-options) - :title (tr "inspect.attributes.typography.font-style")} - (let [basic-variant-options (->> (:variants font) - (map (fn [variant] - {:value (:id variant) - :key (pr-str variant) - :label (:name variant)}))) - variant-options (if (or (= font-variant-id :multiple) (= font-variant-id "mixed")) - (conj basic-variant-options - {:value "" - :key :multiple-variants - :label "--"}) - basic-variant-options) - font-variant-value (attr->string font-variant-id) - font-variant-value (if (= font-variant-value "mixed") "" font-variant-value)] - - ;; TODO Add disabled mode - [:& select - {:class (stl/css :font-variant-select) - :default-value font-variant-value - :options variant-options - :on-change on-font-variant-change - :on-blur on-blur}])]]])) - -(mf/defc spacing-options - {::mf/wrap-props false} - [{:keys [values on-change on-blur]}] - (let [{:keys [line-height - letter-spacing]} values - line-height (or line-height "1.2") - letter-spacing (or letter-spacing "0") - handle-change - (fn [value attr] - (on-change {attr (str value)}))] - - [:div {:class (stl/css :spacing-options)} - [:div {:class (stl/css :line-height) - :title (tr "inspect.attributes.typography.line-height")} - [:span {:class (stl/css :icon) - :alt (tr "workspace.options.text-options.line-height")} - deprecated-icon/text-lineheight] - [:> numeric-input* - {:min -200 - :max 200 - :step 0.1 - :default-value "1.2" - :class (stl/css :line-height-input) - :aria-label (tr "inspect.attributes.typography.line-height") - :value (attr->string line-height) - :placeholder (if (= :multiple line-height) (tr "settings.multiple") "--") - :nillable (= :multiple line-height) - :on-change #(handle-change % :line-height) - :on-blur on-blur}]] - - [:div {:class (stl/css :letter-spacing) - :title (tr "inspect.attributes.typography.letter-spacing")} - [:span - {:class (stl/css :icon) - :alt (tr "workspace.options.text-options.letter-spacing")} - deprecated-icon/text-letterspacing] - [:> numeric-input* - {:min -200 - :max 200 - :step 0.1 - :default-value "0" - :class (stl/css :letter-spacing-input) - :aria-label (tr "inspect.attributes.typography.letter-spacing") - :value (attr->string letter-spacing) - :placeholder (if (= :multiple letter-spacing) (tr "settings.multiple") "--") - :on-change #(handle-change % :letter-spacing) - :nillable (= :multiple letter-spacing) - :on-blur on-blur}]]])) - -(mf/defc text-transform-options - {::mf/wrap-props false} - [{:keys [values on-change on-blur]}] - (let [text-transform (or (:text-transform values) "none") - unset-value (if (features/active-feature? @st/state "text-editor/v2") "none" "unset") - handle-change - (fn [type] - (if (= text-transform type) - (on-change {:text-transform unset-value}) - (on-change {:text-transform type})) - (when (some? on-blur) (on-blur)))] - - [:div {:class (stl/css :text-transform)} - [:& radio-buttons {:selected text-transform - :on-change handle-change - :name "text-transform"} - [:& radio-button {:icon i/text-uppercase - :type "checkbox" - :title (tr "inspect.attributes.typography.text-transform.uppercase") - :value "uppercase" - :id "text-transform-uppercase"}] - [:& radio-button {:icon i/text-mixed - :type "checkbox" - :value "capitalize" - :title (tr "inspect.attributes.typography.text-transform.capitalize") - :id "text-transform-capitalize"}] - [:& radio-button {:icon i/text-lowercase - :type "checkbox" - :title (tr "inspect.attributes.typography.text-transform.lowercase") - :value "lowercase" - :id "text-transform-lowercase"}]]])) - -(mf/defc text-options - {::mf/wrap-props false} - [{: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] - [:> text-transform-options opts]]])) - (mf/defc typography-advanced-options {::mf/wrap [mf/memo]} [{:keys [visible? typography editable? name-input-ref on-close on-change on-name-blur diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs index d663e9defe..9b622745d2 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs @@ -19,7 +19,7 @@ [app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.forms :as fc] - [app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]] + [app.main.ui.workspace.sidebar.options.menus.text-shared :refer [font-selector*]] [app.util.dom :as dom] [app.util.forms :as fm] [app.util.i18n :refer [tr]]