diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index d2be88e40..3998e049d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -9,9 +9,9 @@ (:require [app.common.data :as d] [app.main.data.modal :as modal] + [app.main.ui.workspace.tokens.theme-select :refer [theme-select]] [app.main.data.tokens :as dt] [app.main.data.tokens :as wdt] - [app.main.ui.components.select :refer [select]] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.title-bar :refer [title-bar]] @@ -197,7 +197,7 @@ [:div {:class (stl/css :theme-sidebar)} [:span {:class (stl/css :themes-header)} "Themes"] [:div {:class (stl/css :theme-select-wrapper)} - [:& select + [:& theme-select {:default-value (some-> active-theme-ids first) :class (stl/css :select-format-wrapper) :options (mapcat (fn [[_ xs]] diff --git a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs new file mode 100644 index 000000000..f8ac1f403 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs @@ -0,0 +1,129 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.workspace.tokens.theme-select + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.uuid :as uuid] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [rumext.v2 :as mf])) + + +(defn- as-key-value + [item] + (if (map? item) + [(:value item) (:label item) (:icon item)] + [item item item])) + +(mf/defc theme-select + [{:keys [default-value options class dropdown-class is-open? on-change on-pointer-enter-option on-pointer-leave-option disabled]}] + (let [label-index (mf/with-memo [options] + (into {} (map as-key-value) options)) + + state* (mf/use-state + {:id (uuid/next) + :is-open? (or is-open? false) + :current-value default-value}) + + state (deref state*) + current-id (get state :id) + current-value (get state :current-value) + current-label (get label-index current-value) + is-open? (:is-open? state) + + dropdown-element* (mf/use-ref nil) + dropdown-direction* (mf/use-state "down") + dropdown-direction-change* (mf/use-ref 0) + + open-dropdown + (mf/use-fn + (mf/deps disabled) + (fn [] + (when-not disabled + (swap! state* assoc :is-open? true)))) + + close-dropdown (mf/use-fn #(swap! state* assoc :is-open? false)) + + select-item + (mf/use-fn + (mf/deps on-change) + (fn [event] + (let [value (-> (dom/get-current-target event) + (dom/get-data "value") + (d/read-string))] + + (swap! state* assoc :current-value value) + (when (fn? on-change) + (on-change value))))) + + highlight-item + (mf/use-fn + (mf/deps on-pointer-enter-option) + (fn [event] + (when (fn? on-pointer-enter-option) + (let [value (-> (dom/get-current-target event) + (dom/get-data "value") + (d/read-string))] + (on-pointer-enter-option value))))) + + unhighlight-item + (mf/use-fn + (mf/deps on-pointer-leave-option) + (fn [event] + (when (fn? on-pointer-leave-option) + (let [value (-> (dom/get-current-target event) + (dom/get-data "value") + (d/read-string))] + (on-pointer-leave-option value)))))] + + (mf/with-effect [default-value] + (swap! state* assoc :current-value default-value)) + + (mf/with-effect [is-open? dropdown-element*] + (let [dropdown-element (mf/ref-val dropdown-element*)] + (when (and (= 0 (mf/ref-val dropdown-direction-change*)) dropdown-element) + (let [is-outside? (dom/is-element-outside? dropdown-element)] + (reset! dropdown-direction* (if is-outside? "up" "down")) + (mf/set-ref-val! dropdown-direction-change* (inc (mf/ref-val dropdown-direction-change*))))))) + + (let [selected-option (first (filter #(= (:value %) default-value) options)) + current-icon (:icon selected-option) + current-icon-ref (i/key->icon current-icon)] + [:div {:on-click open-dropdown + :class (dm/str (stl/css-case :custom-select true + :disabled disabled + :icon (some? current-icon-ref)) + " " class)} + (when (and current-icon current-icon-ref) + [:span {:class (stl/css :current-icon)} current-icon-ref]) + [:span {:class (stl/css :current-label)} current-label] + [:span {:class (stl/css :dropdown-button)} i/arrow] + [:& dropdown {:show is-open? :on-close close-dropdown} + [:ul {:ref dropdown-element* :data-direction @dropdown-direction* + :class (dm/str dropdown-class " " (stl/css :custom-select-dropdown))} + (for [[index item] (d/enumerate options)] + (if (= :separator item) + [:li {:class (dom/classnames (stl/css :separator) true) + :key (dm/str current-id "-" index)}] + (let [[value label icon] (as-key-value item) + icon-ref (i/key->icon icon)] + [:li + {:key (dm/str current-id "-" index) + :class (stl/css-case + :checked-element true + :disabled (:disabled item) + :is-selected (= value current-value)) + :data-value (pr-str value) + :on-pointer-enter highlight-item + :on-pointer-leave unhighlight-item + :on-click select-item} + (when (and icon icon-ref) [:span {:class (stl/css :icon)} icon-ref]) + [:span {:class (stl/css :label)} label] + [:span {:class (stl/css :check-icon)} i/tick]])))]]]))) diff --git a/frontend/src/app/main/ui/workspace/tokens/theme_select.scss b/frontend/src/app/main/ui/workspace/tokens/theme_select.scss new file mode 100644 index 000000000..68af4ae36 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/theme_select.scss @@ -0,0 +1,129 @@ +// 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 + +@import "refactor/common-refactor.scss"; + +.custom-select { + --border-color: var(--menu-background-color); + --bg-color: var(--menu-background-color); + --icon-color: var(--icon-foreground); + --text-color: var(--menu-foreground-color); + @extend .new-scrollbar; + @include bodySmallTypography; + position: relative; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + height: $s-32; + width: 100%; + margin: 0; + padding: $s-8; + border-radius: $br-8; + background-color: var(--bg-color); + border: $s-1 solid var(--border-color); + color: var(--text-color); + cursor: pointer; + + &.icon { + grid-template-columns: auto 1fr auto; + } + + &:hover { + --bg-color: var(--menu-background-color-hover); + --border-color: var(--menu-background-color); + --icon-color: var(--menu-foreground-color-hover); + } + + &:focus { + --bg-color: var(--menu-background-color-focus); + --border-color: var(--menu-background-focus); + } +} + +.disabled { + --bg-color: var(--menu-background-color-disabled); + --border-color: var(--menu-border-color-disabled); + --icon-color: var(--menu-foreground-color-disabled); + --text-color: var(--menu-foreground-color-disabled); + pointer-events: none; + cursor: default; +} + +.dropdown-button { + @include flexCenter; + svg { + @extend .button-icon-small; + transform: rotate(90deg); + stroke: var(--icon-color); + } +} + +.current-icon { + @include flexCenter; + width: $s-24; + padding-right: $s-4; + svg { + @extend .button-icon-small; + stroke: var(--icon-foreground); + } +} + +.custom-select-dropdown { + @extend .dropdown-wrapper; + .separator { + margin: 0; + height: $s-12; + border-block-start: $s-1 solid var(--dropdown-separator-color); + } +} + +.custom-select-dropdown[data-direction="up"] { + bottom: $s-32; + top: auto; +} + +.checked-element { + @extend .dropdown-element-base; + .icon { + @include flexCenter; + height: $s-24; + width: $s-24; + padding-right: $s-4; + svg { + @extend .button-icon; + stroke: var(--icon-foreground); + } + } + + .label { + flex-grow: 1; + width: 100%; + } + + .check-icon { + @include flexCenter; + svg { + @extend .button-icon-small; + visibility: hidden; + stroke: var(--icon-foreground); + } + } + + &.is-selected { + color: var(--menu-foreground-color); + .check-icon svg { + stroke: var(--menu-foreground-color); + visibility: visible; + } + } + &.disabled { + display: none; + } +} + +.current-label { + @include textEllipsis; +}