♻️ Divide components into different files

This commit is contained in:
Eva Marco 2026-04-08 12:59:12 +02:00
parent c10f945473
commit 3e12af47f0
5 changed files with 1013 additions and 443 deletions

View File

@ -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]]

View File

@ -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]]]))

View File

@ -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;
}
}

View File

@ -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

View File

@ -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]]