Add new layers panel UI design

This commit is contained in:
Eva 2023-03-15 18:02:57 +01:00 committed by Alonso Torres
parent 90fb619dfc
commit 86b0e95458
191 changed files with 3998 additions and 1037 deletions

View file

@ -4,12 +4,14 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.components.context-menu-a11y
(ns app.main.ui.components.context-menu-a11y.context-menu-a11y
(:require-macros [app.main.style :refer [css]])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.refs :as refs]
[app.main.ui.components.dropdown :refer [dropdown']]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@ -36,15 +38,15 @@
on-click (gobj/get props "on-click")
on-key-down (gobj/get props "on-key-down")
id (gobj/get props "id")
klass (gobj/get props "klass")
key (gobj/get props "key")
klass (gobj/get props "class")
key-index (gobj/get props "key-index")
data-test (gobj/get props "data-test")]
[:li {:id id
:class klass
:tab-index "0"
:on-key-down on-key-down
:on-click on-click
:key key
:key key-index
:role "menuitem"
:data-test data-test}
children]))
@ -54,22 +56,24 @@
[props]
(assert (fn? (gobj/get props "on-close")) "missing `on-close` prop")
(assert (boolean? (gobj/get props "show")) "missing `show` prop")
(assert (vector? (gobj/get props "options")) "missing `options` prop")
(let [open? (gobj/get props "show")
on-close (gobj/get props "on-close")
options (gobj/get props "options")
is-selectable (gobj/get props "selectable")
selected (gobj/get props "selected")
top (gobj/get props "top" 0)
left (gobj/get props "left" 0)
fixed? (gobj/get props "fixed?" false)
min-width? (gobj/get props "min-width?" false)
origin (gobj/get props "origin")
route (mf/deref refs/route)
in-dashboard? (= :dashboard-projects (:name (:data route)))
local (mf/use-state {:offset-y 0
:offset-x 0
:levels nil})
(assert (vector? (gobj/get props "options")) "missing `options` prop")
(let [open? (gobj/get props "show")
on-close (gobj/get props "on-close")
options (gobj/get props "options")
is-selectable (gobj/get props "selectable")
selected (gobj/get props "selected")
top (gobj/get props "top" 0)
left (gobj/get props "left" 0)
fixed? (gobj/get props "fixed?" false)
min-width? (gobj/get props "min-width?" false)
workspace? (gobj/get props "workspace?" false)
origin (gobj/get props "origin")
route (mf/deref refs/route)
new-css-system (mf/use-ctx ctx/new-css-system)
in-dashboard? (= :dashboard-projects (:name (:data route)))
local (mf/use-state {:offset-y 0
:offset-x 0
:levels nil})
on-local-close
(mf/use-callback
@ -79,7 +83,7 @@
(on-close)))
props (obj/merge props #js {:on-close on-local-close})
ids (generate-ids-group (:options (last (:levels @local))) (:parent-option (last (:levels @local))))
check-menu-offscreen
(mf/use-callback
@ -190,64 +194,100 @@
(when (and open? (some? (:levels @local)))
[:> dropdown' props
(let [level (-> @local :levels peek)
original-options (:options level)
parent-original (:parent-option level)]
[:div.context-menu {:class (dom/classnames :is-open open?
:fixed fixed?
:is-selectable is-selectable)
:style {:top (+ top (:offset-y @local))
:left (+ left (:offset-x @local))}
:on-key-down (on-key-down original-options parent-original)}
(let [level (-> @local :levels peek)]
[:ul.context-menu-items {:class (dom/classnames :min-width min-width?)
:role "menu"
:ref check-menu-offscreen}
(when-let [parent-option (:parent-option level)]
[:*
[:& context-menu-a11y-item
{:id "go-back-sub-option"
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
[:div.context-menu-action.submenu-back
{:data-no-close true
:on-click exit-submenu}
[:span i/arrow-slide]
parent-option]]
[:li.separator]])
(for [[index option] (d/enumerate (:options level))]
[:div {:class (if (and new-css-system workspace?)
(dom/classnames (css :is-selectable) is-selectable
(css :context-menu) true
(css :is-open) open?
(css :fixed) fixed?)
(dom/classnames :is-selectable is-selectable
:context-menu true
:is-open open?
:fixed fixed?))
:style {:top (+ top (:offset-y @local))
:left (+ left (:offset-x @local))}
:on-key-down (on-key-down original-options parent-original)}
(let [level (-> @local :levels peek)]
[:ul {:class (if (and new-css-system workspace?)
(dom/classnames (css :min-width) min-width?
(css :context-menu-items) true)
(dom/classnames :min-width min-width?
:context-menu-items true))
:role "menu"
:ref check-menu-offscreen}
(when-let [parent-option (:parent-option level)]
[:*
[:& context-menu-a11y-item
{:id "go-back-sub-option"
:class (dom/classnames (css :context-menu-item) (and new-css-system workspace?))
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
[:div {:class (if (and new-css-system workspace?)
(dom/classnames (css :context-menu-action) true
(css :submenu-back) true)
(dom/classnames :context-menu-action true
:submenu-back true))
:data-no-close true
:on-click exit-submenu}
[:span {:class (dom/classnames (css :submenu-icon-back) (and new-css-system workspace?))}
(if (and new-css-system workspace?)
i/arrow-refactor
i/arrow-slide)]
parent-option]]
[:li {:class (if (and new-css-system workspace?)
(dom/classnames (css :separator) true)
(dom/classnames :separator true))}]])
(for [[index option] (d/enumerate (:options level))]
(let [option-name (:option-name option)
id (:id option)
sub-options (:sub-options option)
option-handler (:option-handler option)
data-test (:data-test option)]
(when option-name
(if (= option-name :separator)
[:li {:key (dm/str "context-item-" index)
:class (if (and new-css-system workspace?)
(dom/classnames (css :separator) true)
(dom/classnames :separator true))}]
[:& context-menu-a11y-item
{:id id
:class (if (and new-css-system workspace?)
(dom/classnames (css :is-selected) (and selected (= option-name selected))
(css :context-menu-item) true)
(dom/classnames :is-selected (and selected (= option-name selected))))
:key-index (dm/str "context-item-" index)
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
(if-not sub-options
[:a {:class (if (and new-css-system workspace?)
(dom/classnames (css :context-menu-action) true)
(dom/classnames :context-menu-action true))
:on-click #(do (dom/stop-propagation %)
(on-close)
(option-handler %))
:data-test data-test}
(if (and in-dashboard? (= option-name "Default"))
(tr "dashboard.default-team-name")
option-name)]
[:a {:class (if (and new-css-system workspace?)
(dom/classnames (css :context-menu-action) true
(css :submenu) true)
(dom/classnames :context-menu-action true
:submenu true))
:data-no-close true
:on-click (enter-submenu option-name sub-options)
:data-test data-test}
option-name
[:span {:class (dom/classnames (css :submenu-icon) (and new-css-system workspace?))}
(if (and new-css-system workspace?)
i/arrow-refactor
i/arrow-slide)]])]))))])])])))
(let [option-name (:option-name option)
id (:id option)
sub-options (:sub-options option)
option-handler (:option-handler option)
data-test (:data-test option)]
(when option-name
(if (= option-name :separator)
[:li.separator {:key (dm/str "context-item-" index)}]
[:& context-menu-a11y-item
{:id id
:class (dom/classnames :is-selected (and selected (= option-name selected)))
:key (dm/str "context-item-" index)
:tab-index "0"
:on-key-down (fn [event]
(dom/prevent-default event))}
(if-not sub-options
[:a.context-menu-action {:on-click #(do (dom/stop-propagation %)
(on-close)
(option-handler %))
:data-test data-test}
(if (and in-dashboard? (= option-name "Default"))
(tr "dashboard.default-team-name")
option-name)]
[:a.context-menu-action.submenu
{:data-no-close true
:on-click (enter-submenu option-name sub-options)
:data-test data-test}
option-name
[:span i/arrow-slide]])]))))])])])))
(mf/defc context-menu-a11y
{::mf/wrap-props false}
@ -255,6 +295,6 @@
(assert (fn? (gobj/get props "on-close")) "missing `on-close` prop")
(assert (boolean? (gobj/get props "show")) "missing `show` prop")
(assert (vector? (gobj/get props "options")) "missing `options` prop")
(when (gobj/get props "show")
(mf/element context-menu-a11y' props)))

View file

@ -0,0 +1 @@
{"button-primary":"context_menu_a11y_context_menu_a11y_button-primary_-nqKB","button-secondary":"context_menu_a11y_context_menu_a11y_button-secondary_3sivR","button-icon":"context_menu_a11y_context_menu_a11y_button-icon_45j80","button-icon-small":"context_menu_a11y_context_menu_a11y_button-icon-small_TNORx","context-menu":"context_menu_a11y_context_menu_a11y_context-menu_HLzPl","context-menu-items":"context_menu_a11y_context_menu_a11y_context-menu-items_r2JIA","context-menu-item":"context_menu_a11y_context_menu_a11y_context-menu-item_KB64Q","context-menu-action":"context_menu_a11y_context_menu_a11y_context-menu-action_x7nPU","submenu-back":"context_menu_a11y_context_menu_a11y_submenu-back_8iOw0","submenu-icon-back":"context_menu_a11y_context_menu_a11y_submenu-icon-back_vlCP7","submenu":"context_menu_a11y_context_menu_a11y_submenu_pUX19","submenu-icon":"context_menu_a11y_context_menu_a11y_submenu-icon_mlof4","is-open":"context_menu_a11y_context_menu_a11y_is-open_ASqQk","fixed":"context_menu_a11y_context_menu_a11y_fixed_5h8sL","separator":"context_menu_a11y_context_menu_a11y_separator_b1CzA","min-width":"context_menu_a11y_context_menu_a11y_min-width_jirG8","is-selected":"context_menu_a11y_context_menu_a11y_is-selected_jihDn","is-selectable":"context_menu_a11y_context_menu_a11y_is-selectable_wqvJa"}

View file

@ -0,0 +1,127 @@
// 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";
.context-menu {
position: relative;
visibility: hidden;
opacity: 0;
z-index: $z-index-2;
&.is-open {
position: relative;
display: block;
opacity: 1;
visibility: visible;
}
&.fixed {
position: fixed;
}
.context-menu-items {
position: absolute;
top: $s-12;
left: calc(-1 * $s-6);
max-height: $s-480;
min-width: $s-96;
margin: 0;
padding: $s-4;
border-radius: $br8;
background-color: var(--menu-background-color);
box-shadow: 0px 0px $s-12 0px var(--menu-shadow-color);
overflow: auto;
& .separator {
height: $s-12;
}
&.min-width {
min-width: $s-192;
}
.context-menu-item {
display: flex;
.context-menu-action {
@include titleTipography;
display: flex;
align-items: center;
justify-content: flex-start;
height: $s-28;
width: 100%;
padding: $s-6;
border-radius: $br8;
white-space: nowrap;
color: var(--menu-foreground-color);
&.submenu {
display: flex;
align-items: center;
justify-content: space-between;
.submenu-icon {
margin-left: 0.5rem;
svg {
@extend .button-icon-small;
stroke: var(--menu-foreground-color);
}
}
}
&.submenu-back {
display: flex;
align-items: center;
font-weight: $fw700;
.submenu-icon-back svg {
@extend .button-icon-small;
stroke: var(--menu-foreground-color);
transform: rotate(180deg);
}
}
}
&:hover .context-menu-action {
background-color: var(--menu-background-color-hover);
text-decoration: none;
color: var(--menu-foreground-color-hover);
&.submenu .submenu-icon svg {
stroke: var(--menu-foreground-color-hover);
}
&.submenu-back .submenu-icon-back svg {
stroke: var(--menu-foreground-color-hover);
}
}
&:focus {
outline: none;
}
&:focus-visible {
outline: none;
.context-menu-action {
border: 1px solid var(--menu-border-color-focus);
background-color: var(--menu-background-color-focus);
text-decoration: none;
color: var(--menu-foreground-color-focus);
&.submenu .submenu-icon svg {
stroke: var(--menu-foreground-color-focus);
}
&.submenu-back .submenu-icon-back svg {
stroke: var(--menu-foreground-color-focus);
}
}
}
}
.is-selected .context-menu-action {
padding-left: $s-28;
background-image: url(/images/icons/tick.svg);
background-repeat: no-repeat;
background-position: 5% 48%;
background-size: $s-12;
font-weight: $fw700;
}
}
&.is-selectable {
.context-menu-action {
padding-left: 1.5rem;
}
}
}

View file

@ -0,0 +1,63 @@
;; 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.components.shape-icon-refactor
(:require
[app.common.types.component :as ctk]
[app.common.types.shape.layout :as ctl]
[app.main.ui.icons :as i]
[rumext.v2 :as mf]))
(mf/defc element-icon-refactor
[{:keys [shape main-instance?] :as props}]
(if (ctk/instance-root? shape)
(if main-instance?
i/component-refactor
i/copy-refactor)
(case (:type shape)
:frame (cond
(and (ctl/flex-layout? shape) (ctl/col? shape))
i/flex-vertical-refactor
(and (ctl/flex-layout? shape) (ctl/row? shape))
i/flex-horizontal-refactor
;; TODO: GRID ICON
:else
i/board-refactor)
;; TODO -> THUMBNAIL ICON
:image i/img-refactor
:line i/path-refactor
:circle i/elipse-refactor
:path i/path-refactor
:rect i/rectangle-refactor
:text i/text-refactor
:group (if (:masked-group? shape)
i/mask-refactor
i/group-refactor)
:bool (case (:bool-type shape)
:difference i/boolean-difference-refactor
:exclude i/boolean-exclude-refactor
:intersection i/boolean-intersection-refactor
#_:default i/boolean-union-refactor)
:svg-raw i/file-svg
nil)))
(mf/defc element-icon-refactor-by-type
[{:keys [type main-instance?] :as props}]
(if main-instance?
i/component-refactor
(case type
:frame i/board-refactor
:image i/img-refactor
:shape i/path-refactor
:text i/text-refactor
:mask i/mask-refactor
:group i/group-refactor
nil)))

View file

@ -0,0 +1,89 @@
;; 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.components.tab-container.tab-container
(:require-macros [app.main.style :refer [css]])
(:require
[app.common.data :as d]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc tab-element
{::mf/wrap-props false}
[props]
(let [children (unchecked-get props "children")
new-css-system (mf/use-ctx ctx/new-css-system)]
[:div {:class (if new-css-system
(dom/classnames (css :tab-element) true)
(dom/classnames :tab-element true))}
children]))
(mf/defc tab-container
{::mf/wrap-props false}
[props]
(let [children (->>
(unchecked-get props "children")
(filter some?))
selected (unchecked-get props "selected")
on-change (unchecked-get props "on-change-tab")
collapsable? (unchecked-get props "collapsable?")
handle-collapse (unchecked-get props "handle-collapse")
state (mf/use-state #(or selected (-> children first .-props .-id)))
selected (or selected @state)
new-css-system (mf/use-ctx ctx/new-css-system)
select-fn
(mf/use-fn
(mf/deps on-change)
(fn [event]
(let [id (d/read-string (.. event -target -dataset -id))]
(reset! state id)
(when (fn? on-change) (on-change id)))))]
[:div {:class (if new-css-system
(dom/classnames (css :tab-container) true)
(dom/classnames :tab-container true))}
[:div {:class (if new-css-system
(dom/classnames (css :tab-container-tabs) true)
(dom/classnames :tab-container-tabs true))}
(when (and new-css-system collapsable?)
[:button
{:on-click handle-collapse
:class (dom/classnames (css :collapse-sidebar) true)
:aria-label (tr "workspace.sidebar.collapse")}
i/arrow-refactor])
(if new-css-system
[:div {:class (dom/classnames (css :tab-container-tab-wrapper) new-css-system)}
(for [tab children]
(let [props (.-props tab)
id (.-id props)
title (.-title props)]
[:div
{:key (str/concat "tab-" (d/name id))
:data-id (pr-str id)
:on-click select-fn
:class (dom/classnames (css :tab-container-tab-title) true
(css :current) (= selected id))}
title]))]
(for [tab children]
(let [props (.-props tab)
id (.-id props)
title (.-title props)]
[:div.tab-container-tab-title
{:key (str/concat "tab-" (d/name id))
:data-id (pr-str id)
:on-click select-fn
:class (when (= selected id) "current")}
title])))]
[:div {:class (if new-css-system
(dom/classnames (css :tab-container-content) true)
(dom/classnames :tab-container-content true))}
(d/seek #(= selected (-> % .-props .-id)) children)]]))

View file

@ -0,0 +1 @@
{"button-primary":"tab_container_tab_container_button-primary_83Zqm","button-secondary":"tab_container_tab_container_button-secondary_lnkfT","button-icon":"tab_container_tab_container_button-icon_9pt7Y","button-icon-small":"tab_container_tab_container_button-icon-small_A8MNz","tab-container":"tab_container_tab_container_tab-container_UElWL","tab-container-content":"tab_container_tab_container_tab-container-content_5dioy","tab-element":"tab_container_tab_container_tab-element_ehGDK","tab-container-tabs":"tab_container_tab_container_tab-container-tabs_Vrl6C","tab-container-tab-wrapper":"tab_container_tab_container_tab-container-tab-wrapper_-g0lU","tab-container-tab-title":"tab_container_tab_container_tab-container-tab-title_lR2I4","current":"tab_container_tab_container_current_jHyvE","collapse-sidebar":"tab_container_tab_container_collapse-sidebar_cuRC2","collapsed":"tab_container_tab_container_collapsed_KWhAl"}

View file

@ -0,0 +1,89 @@
// 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";
.tab-container {
display: grid;
grid-template-rows: auto 1fr;
grid-template-columns: 100%;
height: 100%;
.tab-container-content {
overflow-y: auto;
overflow-x: hidden;
}
.tab-element {
height: 100%;
}
}
.tab-container-tabs {
display: flex;
align-items: center;
flex-direction: row;
gap: $s-2;
height: $s-32;
margin: $s-2 $s-2 0 $s-2;
padding: $s-2;
border-radius: $br8;
background: var(--color-background-secondary);
cursor: pointer;
font-size: $fs12;
.tab-container-tab-wrapper {
@include flexCenter;
flex-direction: row;
height: 100%;
width: 100%;
gap: $s-2;
.tab-container-tab-title {
@include flexCenter;
@include tabTitleTipography;
height: $s-28;
width: 100%;
margin: 0;
border-radius: $br5;
background-color: transparent;
color: var(--tab-foreground-color);
&.current,
&.current:hover {
background: var(--tab-background-color-selected);
color: var(--tab-foreground-color-selected);
}
&:hover {
color: var(--tab-foreground-color-hover);
}
}
}
.collapse-sidebar {
@include flexCenter;
@include buttonStyle;
height: 100%;
width: $s-24;
padding: 0;
border-radius: $br5;
svg {
@include flexCenter;
height: 12px;
width: 16px;
stroke: var(--icon-foreground);
transform: rotate(180deg);
fill: none;
color: transparent;
}
&:hover {
svg {
stroke: var(--icon-foreground-hover);
}
}
&.collapsed {
svg {
transform: rotate(0deg);
}
}
}
}

View file

@ -4,20 +4,20 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.components.tab-container
(ns app.main.ui.components.tabs-container
(:require
[app.common.data :as d]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc tab-element
(mf/defc tabs-element
{::mf/wrap-props false}
[props]
(let [children (unchecked-get props "children")]
[:div.tab-element
[:div.tab-element-content children]]))
(mf/defc tab-container
(mf/defc tabs-container
{::mf/wrap-props false}
[props]
(let [children (->>

View file

@ -0,0 +1,23 @@
;; 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.components.tests.test-component
(:require-macros [app.main.style :refer [css]])
(:require
[app.util.keyboard :as kbd]
[rumext.v2 :as mf]))
(mf/defc test-component [{:keys [action icon name ]}]
[:button.test-component
{:class (css :button)
:tab-index "0"
:on-click action
:on-key-down (fn [event]
(when (kbd/enter? event)
(action event)))}
(when icon [:span.logo icon])
name])

View file

@ -0,0 +1 @@
{"button":"tests_test_component_button_8MQZj"}

View file

@ -0,0 +1,4 @@
.button {
color: var(--button-foreground-active);
background-color: var(--button-background-active);
}