mirror of
https://github.com/penpot/penpot.git
synced 2025-06-08 13:11:42 +02:00
Merge pull request #5010 from penpot/eva-replace-tabs-component
♻️ Replace tabs component
This commit is contained in:
commit
4765685440
23 changed files with 512 additions and 729 deletions
|
@ -20,8 +20,8 @@
|
||||||
:git/url "https://github.com/funcool/beicon.git"}
|
:git/url "https://github.com/funcool/beicon.git"}
|
||||||
|
|
||||||
funcool/rumext
|
funcool/rumext
|
||||||
{:git/tag "v2.12"
|
{:git/tag "v2.13"
|
||||||
:git/sha "ab819f5"
|
:git/sha "dc8e1e5"
|
||||||
:git/url "https://github.com/funcool/rumext.git"}
|
:git/url "https://github.com/funcool/rumext.git"}
|
||||||
|
|
||||||
instaparse/instaparse {:mvn/version "1.5.0"}
|
instaparse/instaparse {:mvn/version "1.5.0"}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.debug.components-preview :as cm]
|
|
||||||
[app.main.ui.debug.icons-preview :refer [icons-preview]]
|
[app.main.ui.debug.icons-preview :refer [icons-preview]]
|
||||||
[app.main.ui.frame-preview :as frame-preview]
|
[app.main.ui.frame-preview :as frame-preview]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
|
@ -169,12 +168,6 @@
|
||||||
:layout-name layout
|
:layout-name layout
|
||||||
:key file-id}]])
|
:key file-id}]])
|
||||||
|
|
||||||
|
|
||||||
:debug-components-preview
|
|
||||||
[:div.debug-preview
|
|
||||||
[:h1 "Components preview"]
|
|
||||||
[:& cm/components-preview]]
|
|
||||||
|
|
||||||
:frame-preview
|
:frame-preview
|
||||||
[:& frame-preview/frame-preview]
|
[:& frame-preview/frame-preview]
|
||||||
|
|
||||||
|
|
|
@ -1,270 +0,0 @@
|
||||||
;; 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.debug.components-preview
|
|
||||||
(:require-macros [app.main.style :as stl])
|
|
||||||
(:require
|
|
||||||
[app.common.data :as d]
|
|
||||||
[app.main.data.users :as du]
|
|
||||||
[app.main.refs :as refs]
|
|
||||||
[app.main.store :as st]
|
|
||||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
|
||||||
[app.main.ui.components.search-bar :refer [search-bar]]
|
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
|
||||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
|
||||||
[app.main.ui.icons :as i]
|
|
||||||
[app.util.dom :as dom]
|
|
||||||
[rumext.v2 :as mf]))
|
|
||||||
|
|
||||||
(mf/defc component-wrapper
|
|
||||||
{::mf/wrap-props false}
|
|
||||||
[props]
|
|
||||||
(let [children (unchecked-get props "children")
|
|
||||||
title (unchecked-get props "title")]
|
|
||||||
[:div {:class (stl/css :component)}
|
|
||||||
[:h4 {:class (stl/css :component-name)} title]
|
|
||||||
children]))
|
|
||||||
|
|
||||||
(mf/defc components-preview
|
|
||||||
{::mf/wrap-props false}
|
|
||||||
[]
|
|
||||||
(let [profile (mf/deref refs/profile)
|
|
||||||
initial (mf/with-memo [profile]
|
|
||||||
(update profile :lang #(or % "")))
|
|
||||||
initial-theme (:theme initial)
|
|
||||||
on-change (fn [event]
|
|
||||||
(let [theme (dom/event->value event)
|
|
||||||
data (assoc initial :theme theme)]
|
|
||||||
(st/emit! (du/update-profile data))))
|
|
||||||
colors ["var(--color-background-primary)"
|
|
||||||
"var(--color-background-secondary)"
|
|
||||||
"var(--color-background-tertiary)"
|
|
||||||
"var(--color-background-quaternary)"
|
|
||||||
"var(--color-foreground-primary)"
|
|
||||||
"var(--color-foreground-secondary)"
|
|
||||||
"var(--color-accent-primary)"
|
|
||||||
"var(--color-accent-primary-muted)"
|
|
||||||
"var(--color-accent-secondary)"
|
|
||||||
"var(--color-accent-tertiary)"]
|
|
||||||
|
|
||||||
;; COMPONENTS FNs
|
|
||||||
state* (mf/use-state {:collapsed? true
|
|
||||||
:tab-selected :first
|
|
||||||
:input-value ""
|
|
||||||
:radio-selected "first"})
|
|
||||||
state (deref state*)
|
|
||||||
|
|
||||||
collapsed? (:collapsed? state)
|
|
||||||
toggle-collapsed
|
|
||||||
(mf/use-fn #(swap! state* update :collapsed? not))
|
|
||||||
|
|
||||||
tab-selected (:tab-selected state)
|
|
||||||
set-tab (mf/use-fn #(swap! state* assoc :tab-selected %))
|
|
||||||
|
|
||||||
input-value (:input-value state)
|
|
||||||
radio-selected (:radio-selected state)
|
|
||||||
|
|
||||||
set-radio-selected (mf/use-fn #(swap! state* assoc :radio-selected %))
|
|
||||||
|
|
||||||
update-search
|
|
||||||
(mf/use-fn
|
|
||||||
(fn [value _event]
|
|
||||||
(swap! state* assoc :input-value value)))
|
|
||||||
|
|
||||||
|
|
||||||
on-btn-click (mf/use-fn #(prn "eyy"))]
|
|
||||||
|
|
||||||
[:section.debug-components-preview
|
|
||||||
[:div {:class (stl/css :themes-row)}
|
|
||||||
[:h2 "Themes"]
|
|
||||||
[:select {:label "Select theme color"
|
|
||||||
:name :theme
|
|
||||||
:default "default"
|
|
||||||
:value initial-theme
|
|
||||||
:on-change on-change}
|
|
||||||
[:option {:label "Penpot Dark (default)" :value "default"}]
|
|
||||||
[:option {:label "Penpot Light" :value "light"}]]
|
|
||||||
[:div {:class (stl/css :wrapper)}
|
|
||||||
(for [color colors]
|
|
||||||
[:div {:class (stl/css :color-wrapper)}
|
|
||||||
[:span (d/name color)]
|
|
||||||
[:div {:key color
|
|
||||||
:style {:background color}
|
|
||||||
:class (stl/css :rect)}]])]]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :components-row)}
|
|
||||||
[:h2 {:class (stl/css :title)} "Components"]
|
|
||||||
[:div {:class (stl/css :components-wrapper)}
|
|
||||||
[:div {:class (stl/css :components-group)}
|
|
||||||
[:h3 "Titles"]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Title"}
|
|
||||||
[:& title-bar {:collapsable false
|
|
||||||
:title "Title"}]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Title and action button"}
|
|
||||||
[:& title-bar {:collapsable false
|
|
||||||
:title "Title"
|
|
||||||
:on-btn-click on-btn-click
|
|
||||||
:btn-children i/add}]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Collapsed title and action button"}
|
|
||||||
[:& title-bar {:collapsable true
|
|
||||||
:collapsed collapsed?
|
|
||||||
:on-collapsed toggle-collapsed
|
|
||||||
:title "Title"
|
|
||||||
:on-btn-click on-btn-click
|
|
||||||
:btn-children i/add}]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Collapsed title and children"}
|
|
||||||
[:& title-bar {:collapsable true
|
|
||||||
:collapsed collapsed?
|
|
||||||
:on-collapsed toggle-collapsed
|
|
||||||
:title "Title"}
|
|
||||||
[:& tab-container {:on-change-tab set-tab
|
|
||||||
:selected tab-selected}
|
|
||||||
[:& tab-element {:id :first
|
|
||||||
:title "A tab"}]
|
|
||||||
[:& tab-element {:id :second
|
|
||||||
:title "B tab"}]]]]]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :components-group)}
|
|
||||||
[:h3 "Tabs component"]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "2 tab component"}
|
|
||||||
[:& tab-container {:on-change-tab set-tab
|
|
||||||
:selected tab-selected}
|
|
||||||
[:& tab-element {:id :first :title "First tab"}
|
|
||||||
[:div "This is first tab content"]]
|
|
||||||
|
|
||||||
[:& tab-element {:id :second :title "Second tab"}
|
|
||||||
[:div "This is second tab content"]]]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "3 tab component"}
|
|
||||||
[:& tab-container {:on-change-tab set-tab
|
|
||||||
:selected tab-selected}
|
|
||||||
[:& tab-element {:id :first :title "First tab"}
|
|
||||||
[:div "This is first tab content"]]
|
|
||||||
|
|
||||||
[:& tab-element {:id :second
|
|
||||||
:title "Second tab"}
|
|
||||||
[:div "This is second tab content"]]
|
|
||||||
[:& tab-element {:id :third
|
|
||||||
:title "Third tab"}
|
|
||||||
[:div "This is third tab content"]]]]]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :components-group)}
|
|
||||||
[:h3 "Search bar"]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Search bar only"}
|
|
||||||
[:& search-bar {:on-change update-search
|
|
||||||
:value input-value
|
|
||||||
:placeholder "Test value"}]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Search and button"}
|
|
||||||
[:& search-bar {:on-change update-search
|
|
||||||
:value input-value
|
|
||||||
:placeholder "Test value"}
|
|
||||||
[:button {:class (stl/css :button-secondary)
|
|
||||||
:on-click on-btn-click}
|
|
||||||
"X"]]]]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :components-group)}
|
|
||||||
[:h3 "Radio buttons"]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Two radio buttons (toggle)"}
|
|
||||||
[:& radio-buttons {:selected radio-selected
|
|
||||||
:on-change set-radio-selected
|
|
||||||
:name "listing-style"}
|
|
||||||
[:& radio-button {:icon i/view-as-list
|
|
||||||
:value "first"
|
|
||||||
:id :list}]
|
|
||||||
[:& radio-button {:icon i/flex-grid
|
|
||||||
:value "second"
|
|
||||||
:id :grid}]]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Three radio buttons"}
|
|
||||||
[:& radio-buttons {:selected radio-selected
|
|
||||||
:on-change set-radio-selected
|
|
||||||
:name "listing-style"}
|
|
||||||
[:& radio-button {:icon i/view-as-list
|
|
||||||
:value "first"
|
|
||||||
:id :first}]
|
|
||||||
[:& radio-button {:icon i/flex-grid
|
|
||||||
:value "second"
|
|
||||||
:id :second}]
|
|
||||||
|
|
||||||
[:& radio-button {:icon i/add
|
|
||||||
:value "third"
|
|
||||||
:id :third}]]]
|
|
||||||
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Four radio buttons"}
|
|
||||||
[:& radio-buttons {:selected radio-selected
|
|
||||||
:on-change set-radio-selected
|
|
||||||
:name "listing-style"}
|
|
||||||
[:& radio-button {:icon i/view-as-list
|
|
||||||
:value "first"
|
|
||||||
:id :first}]
|
|
||||||
[:& radio-button {:icon i/flex-grid
|
|
||||||
:value "second"
|
|
||||||
:id :second}]
|
|
||||||
|
|
||||||
[:& radio-button {:icon i/add
|
|
||||||
:value "third"
|
|
||||||
:id :third}]
|
|
||||||
|
|
||||||
[:& radio-button {:icon i/board
|
|
||||||
:value "forth"
|
|
||||||
:id :forth}]]]]
|
|
||||||
[:div {:class (stl/css :components-group)}
|
|
||||||
[:h3 "Buttons"]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Button primary"}
|
|
||||||
[:button {:class (stl/css :button-primary)}
|
|
||||||
"Primary"]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Button primary with icon"}
|
|
||||||
[:button {:class (stl/css :button-primary)}
|
|
||||||
i/add]]
|
|
||||||
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Button secondary"}
|
|
||||||
[:button {:class (stl/css :button-secondary)}
|
|
||||||
"secondary"]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Button secondary with icon"}
|
|
||||||
[:button {:class (stl/css :button-secondary)}
|
|
||||||
i/add]]
|
|
||||||
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Button tertiary"}
|
|
||||||
[:button {:class (stl/css :button-tertiary)}
|
|
||||||
"tertiary"]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Button tertiary with icon"}
|
|
||||||
[:button {:class (stl/css :button-tertiary)}
|
|
||||||
i/add]]]
|
|
||||||
[:div {:class (stl/css :components-group)}
|
|
||||||
[:h3 "Inputs"]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Only input"}
|
|
||||||
[:div {:class (stl/css :input-wrapper)}
|
|
||||||
[:input {:class (stl/css :basic-input)
|
|
||||||
:placeholder "----"}]]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Input with label"}
|
|
||||||
[:div {:class (stl/css :input-wrapper)}
|
|
||||||
[:span {:class (stl/css :input-label)} "label"]
|
|
||||||
[:input {:class (stl/css :basic-input)
|
|
||||||
:placeholder "----"}]]]
|
|
||||||
[:& component-wrapper
|
|
||||||
{:title "Input with icon"}
|
|
||||||
[:div {:class (stl/css :input-wrapper)}
|
|
||||||
[:span {:class (stl/css :input-label)}
|
|
||||||
i/add]
|
|
||||||
[:input {:class (stl/css :basic-input)
|
|
||||||
:placeholder "----"}]]]]]]]))
|
|
|
@ -1,99 +0,0 @@
|
||||||
// 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";
|
|
||||||
|
|
||||||
.themes-row {
|
|
||||||
width: 100%;
|
|
||||||
padding: $s-20;
|
|
||||||
color: var(--color-foreground-primary);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
.wrapper {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: $s-40;
|
|
||||||
background-color: var(--color-background-primary);
|
|
||||||
width: 100%;
|
|
||||||
padding: $s-20;
|
|
||||||
.rect {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border: $s-1 solid var(--color-foreground-primary);
|
|
||||||
padding: $s-20;
|
|
||||||
height: $s-96;
|
|
||||||
min-width: $s-152;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.color-wrapper {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto $s-96;
|
|
||||||
}
|
|
||||||
|
|
||||||
.components-row {
|
|
||||||
color: var(--color-foreground-primary);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 $s-20;
|
|
||||||
.title {
|
|
||||||
padding: $s-20;
|
|
||||||
}
|
|
||||||
.components-wrapper {
|
|
||||||
padding: $s-20;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $s-20;
|
|
||||||
.components-group {
|
|
||||||
@include flexCenter;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: $s-8;
|
|
||||||
h3 {
|
|
||||||
@include bodySmallTypography;
|
|
||||||
font-size: $fs-24;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.component {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $s-8;
|
|
||||||
width: $s-240;
|
|
||||||
max-height: $s-80;
|
|
||||||
margin-bottom: $s-16;
|
|
||||||
.component-name {
|
|
||||||
@include uppercaseTitleTipography;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button-primary {
|
|
||||||
@extend .button-primary;
|
|
||||||
height: $s-32;
|
|
||||||
svg {
|
|
||||||
@extend .button-icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button-secondary {
|
|
||||||
@extend .button-secondary;
|
|
||||||
height: $s-32;
|
|
||||||
svg {
|
|
||||||
@extend .button-icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button-tertiary {
|
|
||||||
@extend .button-tertiary;
|
|
||||||
height: $s-32;
|
|
||||||
svg {
|
|
||||||
@extend .button-icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.input-wrapper {
|
|
||||||
@extend .input-element;
|
|
||||||
@include bodySmallTypography;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,8 +9,8 @@
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.main.style :as stl])
|
[app.main.style :as stl])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
|
[app.util.array :as array]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
@ -19,45 +19,50 @@
|
||||||
(mf/defc tab*
|
(mf/defc tab*
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
::mf/private true}
|
::mf/private true}
|
||||||
[{:keys [selected icon label aria-label id tab-ref] :rest props}]
|
[{:keys [selected icon label aria-label id on-ref] :rest props}]
|
||||||
|
|
||||||
(let [class (stl/css-case :tab true
|
(let [class (stl/css-case :tab true
|
||||||
:selected selected)
|
:selected selected)
|
||||||
props (mf/spread-props props {:class class
|
props (mf/spread-props props
|
||||||
:role "tab"
|
{:class class
|
||||||
:aria-selected selected
|
:role "tab"
|
||||||
:title (or label aria-label)
|
:aria-selected selected
|
||||||
:tab-index (if selected nil -1)
|
:title (or label aria-label)
|
||||||
:ref tab-ref
|
:tab-index (if selected nil -1)
|
||||||
:data-id id})]
|
:ref (fn [node]
|
||||||
|
(on-ref node id))
|
||||||
|
:data-id id
|
||||||
|
;; This prop is to be used for accessibility purposes only.
|
||||||
|
:id id})]
|
||||||
|
|
||||||
[:> "li" {}
|
[:li
|
||||||
[:> "button" props
|
[:> :button props
|
||||||
(when icon
|
(when (some? icon)
|
||||||
[:> icon*
|
[:> icon*
|
||||||
{:id icon
|
{:id icon
|
||||||
:aria-hidden (when label true)
|
:aria-hidden (when label true)
|
||||||
:aria-label (when (not label) aria-label)}])
|
:aria-label (when (not label) aria-label)}])
|
||||||
(when label
|
(when (string? label)
|
||||||
[:span {:class (stl/css-case :tab-text true
|
[:span {:class (stl/css-case :tab-text true
|
||||||
:tab-text-and-icon icon)} label])]]))
|
:tab-text-and-icon icon)}
|
||||||
|
label])]]))
|
||||||
|
|
||||||
(mf/defc tab-nav*
|
(mf/defc tab-nav*
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
::mf/private true}
|
::mf/private true}
|
||||||
[{:keys [tabs-refs tabs selected on-click button-position action-button] :rest props}]
|
[{:keys [on-ref tabs selected on-click button-position action-button] :rest props}]
|
||||||
(let [class (stl/css-case :tab-nav true
|
(let [class (stl/css-case :tab-nav true
|
||||||
:tab-nav-start (= "start" button-position)
|
:tab-nav-start (= "start" button-position)
|
||||||
:tab-nav-end (= "end" button-position))
|
:tab-nav-end (= "end" button-position))
|
||||||
props (mf/spread-props props {:class (stl/css :tab-list)
|
props (mf/spread-props props
|
||||||
:role "tablist"
|
{:class (stl/css :tab-list)
|
||||||
:aria-orientation "horizontal"})]
|
:role "tablist"
|
||||||
[:> "nav" {:class class}
|
:aria-orientation "horizontal"})]
|
||||||
|
[:nav {:class class}
|
||||||
(when (= button-position "start")
|
(when (= button-position "start")
|
||||||
action-button)
|
action-button)
|
||||||
|
|
||||||
[:> "ul" props
|
[:> "ul" props
|
||||||
(for [[index element] (map-indexed vector tabs)]
|
(for [element ^js tabs]
|
||||||
(let [icon (obj/get element "icon")
|
(let [icon (obj/get element "icon")
|
||||||
label (obj/get element "label")
|
label (obj/get element "label")
|
||||||
aria-label (obj/get element "aria-label")
|
aria-label (obj/get element "aria-label")
|
||||||
|
@ -67,94 +72,129 @@
|
||||||
:key (dm/str "tab-" id)
|
:key (dm/str "tab-" id)
|
||||||
:label label
|
:label label
|
||||||
:aria-label aria-label
|
:aria-label aria-label
|
||||||
:selected (= index selected)
|
:selected (= id selected)
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
:id id
|
:on-ref on-ref
|
||||||
:tab-ref (nth tabs-refs index)}]))]
|
:id id}]))]
|
||||||
|
|
||||||
(when (= button-position "end")
|
(when (= button-position "end")
|
||||||
action-button)]))
|
action-button)]))
|
||||||
|
|
||||||
(mf/defc tab-panel*
|
(defn- get-tab
|
||||||
{::mf/props :obj
|
[tabs id]
|
||||||
::mf/private true}
|
(or (array/find #(= id (obj/get % "id")) tabs)
|
||||||
[{:keys [children name] :rest props}]
|
(aget tabs 0)))
|
||||||
(let [props (mf/spread-props props {:class (stl/css :tab-panel)
|
|
||||||
:aria-labelledby name
|
|
||||||
:role "tabpanel"})]
|
|
||||||
[:> "section" props
|
|
||||||
children]))
|
|
||||||
|
|
||||||
(defn- valid-tabs?
|
(defn- get-selected-tab-id
|
||||||
[tabs]
|
[tabs default]
|
||||||
(every? (fn [tab]
|
(let [tab (get-tab tabs default)]
|
||||||
(let [icon (obj/get tab "icon")
|
(obj/get tab "id")))
|
||||||
label (obj/get tab "label")
|
|
||||||
aria-label (obj/get tab "aria-label")]
|
|
||||||
(and (or (not icon) (contains? icon-list icon))
|
|
||||||
(not (and icon (nil? label) (nil? aria-label)))
|
|
||||||
(not (and aria-label (or (nil? icon) label))))))
|
|
||||||
(seq tabs)))
|
|
||||||
|
|
||||||
(def ^:private positions (set '("start" "end")))
|
(def ^:private schema:tab
|
||||||
|
[:and
|
||||||
|
[:map {:title "tab"}
|
||||||
|
[:icon {:optional true}
|
||||||
|
[:and :string [:fn #(contains? icon-list %)]]]
|
||||||
|
[:label {:optional true} :string]
|
||||||
|
[:aria-label {:optional true} :string]
|
||||||
|
[:content some?]]
|
||||||
|
[:fn {:error/message "invalid data: missing required props"}
|
||||||
|
(fn [tab]
|
||||||
|
(or (and (contains? tab :icon)
|
||||||
|
(or (contains? tab :label)
|
||||||
|
(contains? tab :aria-label)))
|
||||||
|
(contains? tab :label)))]])
|
||||||
|
|
||||||
(defn- valid-button-position? [position button]
|
(def ^:private schema:tab-switcher
|
||||||
(or (nil? position) (and (contains? positions position) (some? button))))
|
[:map
|
||||||
|
[:class {:optional true} :string]
|
||||||
|
[:action-button-position {:optional true}
|
||||||
|
[:enum "start" "end"]]
|
||||||
|
[:default-selected {:optional true} :string]
|
||||||
|
[:tabs [:vector {:min 1} schema:tab]]])
|
||||||
|
|
||||||
(mf/defc tab-switcher*
|
(mf/defc tab-switcher*
|
||||||
{::mf/props :obj}
|
{::mf/props :obj
|
||||||
|
::mf/schema schema:tab-switcher}
|
||||||
[{:keys [class tabs on-change-tab default-selected action-button-position action-button] :rest props}]
|
[{:keys [class tabs on-change-tab default-selected action-button-position action-button] :rest props}]
|
||||||
;; TODO: Use a schema to assert the tabs prop -> https://tree.taiga.io/project/penpot/task/8521
|
(let [selected* (mf/use-state #(get-selected-tab-id tabs default-selected))
|
||||||
(assert (valid-tabs? tabs) "unexpected props for tab-switcher")
|
selected (deref selected*)
|
||||||
(assert (valid-button-position? action-button-position action-button) "invalid action-button-position")
|
|
||||||
(let [tab-ids (mapv #(obj/get % "id") tabs)
|
|
||||||
|
|
||||||
active-tab-index* (mf/use-state (or (d/index-of tab-ids default-selected) 0))
|
tabs-nodes-refs (mf/use-ref nil)
|
||||||
active-tab-index (deref active-tab-index*)
|
tabs-ref (mf/use-ref nil)
|
||||||
|
|
||||||
tabs-refs (mapv (fn [_] (mf/use-ref)) tabs)
|
on-click
|
||||||
|
|
||||||
active-tab (nth tabs active-tab-index)
|
|
||||||
panel-content (obj/get active-tab "content")
|
|
||||||
|
|
||||||
handle-click
|
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps on-change-tab tab-ids)
|
(mf/deps on-change-tab)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [id (dom/get-data (dom/get-current-target event) "id")
|
(let [node (dom/get-current-target event)
|
||||||
index (d/index-of tab-ids id)]
|
id (dom/get-data node "id")]
|
||||||
(reset! active-tab-index* index)
|
(reset! selected* id)
|
||||||
|
|
||||||
(when (fn? on-change-tab)
|
(when (fn? on-change-tab)
|
||||||
(on-change-tab id)))))
|
(on-change-tab id)))))
|
||||||
|
|
||||||
|
on-ref
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [node id]
|
||||||
|
(let [refs (or (mf/ref-val tabs-nodes-refs) #js {})
|
||||||
|
refs (if node
|
||||||
|
(obj/set! refs id node)
|
||||||
|
(obj/unset! refs id))]
|
||||||
|
(mf/set-ref-val! tabs-nodes-refs refs))))
|
||||||
|
|
||||||
on-key-down
|
on-key-down
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps tabs-refs active-tab-index)
|
(mf/deps selected)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [len (count tabs-refs)
|
(let [tabs (mf/ref-val tabs-ref)
|
||||||
index (cond
|
len (alength tabs)
|
||||||
(kbd/home? event) 0
|
sel? #(= selected (obj/get % "id"))
|
||||||
(kbd/left-arrow? event) (mod (- active-tab-index 1) len)
|
id (cond
|
||||||
(kbd/right-arrow? event) (mod (+ active-tab-index 1) len))]
|
(kbd/home? event)
|
||||||
(when index
|
(let [tab (aget tabs 0)]
|
||||||
(reset! active-tab-index* index)
|
(obj/get tab "id"))
|
||||||
(dom/focus! (mf/ref-val (nth tabs-refs index)))))))
|
|
||||||
|
(kbd/left-arrow? event)
|
||||||
|
(let [index (array/find-index sel? tabs)
|
||||||
|
index (mod (- index 1) len)
|
||||||
|
tab (aget tabs index)]
|
||||||
|
(obj/get tab "id"))
|
||||||
|
|
||||||
|
(kbd/right-arrow? event)
|
||||||
|
(let [index (array/find-index sel? tabs)
|
||||||
|
index (mod (+ index 1) len)
|
||||||
|
tab (aget tabs index)]
|
||||||
|
(obj/get tab "id")))]
|
||||||
|
|
||||||
|
(when (some? id)
|
||||||
|
(reset! selected* id)
|
||||||
|
(let [nodes (mf/ref-val tabs-nodes-refs)
|
||||||
|
node (obj/get nodes id)]
|
||||||
|
(dom/focus! node))))))
|
||||||
|
|
||||||
class (dm/str class " " (stl/css :tabs))
|
class (dm/str class " " (stl/css :tabs))
|
||||||
|
|
||||||
props (mf/spread-props props {:class class
|
props (mf/spread-props props {:class class
|
||||||
:on-key-down on-key-down})]
|
:on-key-down on-key-down})]
|
||||||
|
|
||||||
[:> "article" props
|
(mf/with-effect [tabs]
|
||||||
[:> "div" {:class (stl/css :padding-wrapper)}
|
(mf/set-ref-val! tabs-ref tabs))
|
||||||
|
|
||||||
|
[:> :article props
|
||||||
|
[:div {:class (stl/css :padding-wrapper)}
|
||||||
[:> tab-nav* {:button-position action-button-position
|
[:> tab-nav* {:button-position action-button-position
|
||||||
:action-button action-button
|
:action-button action-button
|
||||||
:tabs tabs
|
:tabs tabs
|
||||||
:selected active-tab-index
|
:on-ref on-ref
|
||||||
:on-click handle-click
|
:selected selected
|
||||||
:tabs-refs tabs-refs}]]
|
:on-click on-click}]]
|
||||||
|
|
||||||
[:> tab-panel* {:tab-index 0}
|
|
||||||
panel-content]]))
|
|
||||||
|
|
||||||
|
(let [active-tab (get-tab tabs selected)
|
||||||
|
content (obj/get active-tab "content")
|
||||||
|
id (obj/get active-tab "id")]
|
||||||
|
[:section {:class (stl/css :tab-panel)
|
||||||
|
:tab-index 0
|
||||||
|
:role "tabpanel"
|
||||||
|
:aria-labelledby id}
|
||||||
|
content])]))
|
||||||
|
|
|
@ -99,3 +99,9 @@
|
||||||
.tab-text-and-icon {
|
.tab-text-and-icon {
|
||||||
padding-inline: var(--sp-xxs);
|
padding-inline: var(--sp-xxs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab-panel {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -60,8 +60,6 @@
|
||||||
(when *assert*
|
(when *assert*
|
||||||
["/debug/icons-preview" :debug-icons-preview])
|
["/debug/icons-preview" :debug-icons-preview])
|
||||||
|
|
||||||
["/debug/components-preview" :debug-components-preview]
|
|
||||||
|
|
||||||
;; Used for export
|
;; Used for export
|
||||||
["/render-sprite/:file-id" :render-sprite]
|
["/render-sprite/:file-id" :render-sprite]
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
[{:keys [local file page frame index viewer-pagination size share-id]}]
|
[{:keys [local file page frame index viewer-pagination size share-id]}]
|
||||||
(let [inspect-svg-container-ref (mf/use-ref nil)
|
(let [inspect-svg-container-ref (mf/use-ref nil)
|
||||||
current-section* (mf/use-state :info)
|
current-section* (mf/use-state :info)
|
||||||
current-section (deref current-section*)
|
current-section (deref current-section*)
|
||||||
|
|
||||||
can-be-expanded? (= current-section :code)
|
can-be-expanded? (= current-section :code)
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
content (when (= (count shapes) 1)
|
content (when (= (count shapes) 1)
|
||||||
(ctkl/get-component-annotation (first shapes) libraries))]
|
(ctkl/get-component-annotation (first shapes) libraries))]
|
||||||
|
|
||||||
[:div {:class (stl/css :element-options)}
|
[:div {:class (stl/css-case :element-options true
|
||||||
|
:workspace-element-options (= from :workspace))}
|
||||||
(for [[idx option] (map-indexed vector options)]
|
(for [[idx option] (map-indexed vector options)]
|
||||||
[:> (case option
|
[:> (case option
|
||||||
:geometry geometry-panel
|
:geometry geometry-panel
|
||||||
|
|
|
@ -11,6 +11,13 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $s-16;
|
gap: $s-16;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: calc(100vh - #{$s-128}); // TODO: Fix this hardcoded value
|
||||||
padding-top: $s-8;
|
padding-top: $s-8;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-element-options {
|
||||||
|
height: calc(100vh - #{$s-164}); // TODO: Fix this hardcoded value
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,12 @@
|
||||||
.element-options {
|
.element-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: calc(100vh - #{$s-128}); // TODO: Fix this hardcoded value
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: $s-16;
|
padding-bottom: $s-16;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-button {
|
.download-button {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.ui.components.shape-icon :as sir]
|
[app.main.ui.components.shape-icon :as sir]
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
[app.main.ui.ds.tab-switcher :refer [tab-switcher*]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.main.ui.viewer.inspect.attributes :refer [attributes]]
|
[app.main.ui.viewer.inspect.attributes :refer [attributes]]
|
||||||
[app.main.ui.viewer.inspect.code :refer [code]]
|
[app.main.ui.viewer.inspect.code :refer [code]]
|
||||||
|
@ -61,9 +61,9 @@
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps from on-change-section)
|
(mf/deps from on-change-section)
|
||||||
(fn [new-section]
|
(fn [new-section]
|
||||||
(reset! section new-section)
|
(reset! section (keyword new-section))
|
||||||
(when on-change-section
|
(when on-change-section
|
||||||
(on-change-section new-section))))
|
(on-change-section (keyword new-section)))))
|
||||||
|
|
||||||
handle-expand
|
handle-expand
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
@ -74,7 +74,33 @@
|
||||||
navigate-to-help
|
navigate-to-help
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
(dom/open-new-window "https://help.penpot.app/user-guide/inspect/")))]
|
(dom/open-new-window "https://help.penpot.app/user-guide/inspect/")))
|
||||||
|
|
||||||
|
info-content
|
||||||
|
(mf/html [:& attributes {:page-id page-id
|
||||||
|
:objects objects
|
||||||
|
:file-id file-id
|
||||||
|
:frame frame
|
||||||
|
:shapes shapes
|
||||||
|
:from from
|
||||||
|
:libraries libraries
|
||||||
|
:share-id share-id}])
|
||||||
|
|
||||||
|
code-content
|
||||||
|
(mf/html [:& code {:frame frame
|
||||||
|
:shapes shapes
|
||||||
|
:on-expand handle-expand
|
||||||
|
:from from}])
|
||||||
|
|
||||||
|
tabs
|
||||||
|
#js [#js {:label (tr "inspect.tabs.info")
|
||||||
|
:id "info"
|
||||||
|
:content info-content}
|
||||||
|
|
||||||
|
#js {:label (tr "inspect.tabs.code")
|
||||||
|
:data-testid "code"
|
||||||
|
:id "code"
|
||||||
|
:content code-content}]]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps shapes handle-change-tab)
|
(mf/deps shapes handle-change-tab)
|
||||||
|
@ -108,25 +134,11 @@
|
||||||
;; (tr "inspect.tabs.code.selected.text")
|
;; (tr "inspect.tabs.code.selected.text")
|
||||||
[:span {:class (stl/css :layer-title)} (:name first-shape)]])]
|
[:span {:class (stl/css :layer-title)} (:name first-shape)]])]
|
||||||
[:div {:class (stl/css :inspect-content)}
|
[:div {:class (stl/css :inspect-content)}
|
||||||
[:& tab-container {:on-change-tab handle-change-tab
|
|
||||||
:selected @section
|
|
||||||
:content-class (stl/css :tab-content)
|
|
||||||
:header-class (stl/css :tab-header)}
|
|
||||||
[:& tab-element {:id :info :title (tr "inspect.tabs.info")}
|
|
||||||
[:& attributes {:page-id page-id
|
|
||||||
:objects objects
|
|
||||||
:file-id file-id
|
|
||||||
:frame frame
|
|
||||||
:shapes shapes
|
|
||||||
:from from
|
|
||||||
:libraries libraries
|
|
||||||
:share-id share-id}]]
|
|
||||||
|
|
||||||
[:& tab-element {:id :code :title (tr "inspect.tabs.code")}
|
[:> tab-switcher* {:tabs tabs
|
||||||
[:& code {:frame frame
|
:default-selected "info"
|
||||||
:shapes shapes
|
:on-change-tab handle-change-tab
|
||||||
:on-expand handle-expand
|
:class (stl/css :viewer-tab-switcher)}]]]
|
||||||
:from from}]]]]]
|
|
||||||
[:div {:class (stl/css :empty)}
|
[:div {:class (stl/css :empty)}
|
||||||
[:div {:class (stl/css :code-info)}
|
[:div {:class (stl/css :code-info)}
|
||||||
[:span {:class (stl/css :placeholder-icon)}
|
[:span {:class (stl/css :placeholder-icon)}
|
||||||
|
|
|
@ -97,10 +97,6 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content {
|
.viewer-tab-switcher {
|
||||||
scrollbar-gutter: stable;
|
--tabs-nav-padding-inline-end: var(--sp-m);
|
||||||
}
|
|
||||||
|
|
||||||
.tab-header {
|
|
||||||
margin-right: $s-12;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||||
[app.main.ui.components.select :refer [select]]
|
[app.main.ui.components.select :refer [select]]
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
[app.main.ui.ds.foundations.assets.icon :as ic]
|
||||||
|
[app.main.ui.ds.tab-switcher :refer [tab-switcher*]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]]
|
[app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]]
|
||||||
[app.main.ui.workspace.colorpicker.gradients :refer [gradients]]
|
[app.main.ui.workspace.colorpicker.gradients :refer [gradients]]
|
||||||
|
@ -208,7 +209,50 @@
|
||||||
[{:value :linear-gradient :label (tr "media.linear")}
|
[{:value :linear-gradient :label (tr "media.linear")}
|
||||||
{:value :radial-gradient :label (tr "media.radial")}])
|
{:value :radial-gradient :label (tr "media.radial")}])
|
||||||
(when (not disable-image)
|
(when (not disable-image)
|
||||||
[{:value :image :label (tr "media.image")}])))]
|
[{:value :image :label (tr "media.image")}])))
|
||||||
|
|
||||||
|
tabs
|
||||||
|
#js [#js {:aria-label (tr "workspace.libraries.colors.rgba")
|
||||||
|
:icon ic/rgba
|
||||||
|
:id "ramp"
|
||||||
|
:content (mf/html (if picking-color?
|
||||||
|
[:div {:class (stl/css :picker-detail-wrapper)}
|
||||||
|
[:div {:class (stl/css :center-circle)}]
|
||||||
|
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
|
||||||
|
[:& ramp-selector
|
||||||
|
{:color current-color
|
||||||
|
:disable-opacity disable-opacity
|
||||||
|
:on-change handle-change-color
|
||||||
|
:on-start-drag on-start-drag
|
||||||
|
:on-finish-drag on-finish-drag}]))}
|
||||||
|
|
||||||
|
#js {:aria-label "Harmony"
|
||||||
|
:icon ic/rgba-complementary
|
||||||
|
:id "harmony"
|
||||||
|
:content (mf/html (if picking-color?
|
||||||
|
[:div {:class (stl/css :picker-detail-wrapper)}
|
||||||
|
[:div {:class (stl/css :center-circle)}]
|
||||||
|
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
|
||||||
|
[:& harmony-selector
|
||||||
|
{:color current-color
|
||||||
|
:disable-opacity disable-opacity
|
||||||
|
:on-change handle-change-color
|
||||||
|
:on-start-drag on-start-drag
|
||||||
|
:on-finish-drag on-finish-drag}]))}
|
||||||
|
|
||||||
|
#js {:aria-label "HSVA"
|
||||||
|
:icon ic/hsva
|
||||||
|
:id "hsva"
|
||||||
|
:content (mf/html (if picking-color?
|
||||||
|
[:div {:class (stl/css :picker-detail-wrapper)}
|
||||||
|
[:div {:class (stl/css :center-circle)}]
|
||||||
|
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
|
||||||
|
[:& hsva-selector
|
||||||
|
{:color current-color
|
||||||
|
:disable-opacity disable-opacity
|
||||||
|
:on-change handle-change-color
|
||||||
|
:on-start-drag on-start-drag
|
||||||
|
:on-finish-drag on-finish-drag}]))}]]
|
||||||
|
|
||||||
;; Initialize colorpicker state
|
;; Initialize colorpicker state
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
|
@ -306,46 +350,9 @@
|
||||||
:on-selected on-fill-image-selected}]]])
|
:on-selected on-fill-image-selected}]]])
|
||||||
[:*
|
[:*
|
||||||
[:div {:class (stl/css :colorpicker-tabs)}
|
[:div {:class (stl/css :colorpicker-tabs)}
|
||||||
[:& tab-container
|
[:> tab-switcher* {:tabs tabs
|
||||||
{:on-change-tab on-change-tab
|
:default-selected "ramp"
|
||||||
:selected @active-color-tab
|
:on-change-tab on-change-tab}]]
|
||||||
:collapsable false}
|
|
||||||
|
|
||||||
[:& tab-element {:id :ramp :title i/rgba}
|
|
||||||
(if picking-color?
|
|
||||||
[:div {:class (stl/css :picker-detail-wrapper)}
|
|
||||||
[:div {:class (stl/css :center-circle)}]
|
|
||||||
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
|
|
||||||
[:& ramp-selector
|
|
||||||
{:color current-color
|
|
||||||
:disable-opacity disable-opacity
|
|
||||||
:on-change handle-change-color
|
|
||||||
:on-start-drag on-start-drag
|
|
||||||
:on-finish-drag on-finish-drag}])]
|
|
||||||
|
|
||||||
[:& tab-element {:id :harmony :title i/rgba-complementary}
|
|
||||||
(if picking-color?
|
|
||||||
[:div {:class (stl/css :picker-detail-wrapper)}
|
|
||||||
[:div {:class (stl/css :center-circle)}]
|
|
||||||
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
|
|
||||||
[:& harmony-selector
|
|
||||||
{:color current-color
|
|
||||||
:disable-opacity disable-opacity
|
|
||||||
:on-change handle-change-color
|
|
||||||
:on-start-drag on-start-drag
|
|
||||||
:on-finish-drag on-finish-drag}])]
|
|
||||||
|
|
||||||
[:& tab-element {:id :hsva :title i/hsva}
|
|
||||||
(if picking-color?
|
|
||||||
[:div {:class (stl/css :picker-detail-wrapper)}
|
|
||||||
[:div {:class (stl/css :center-circle)}]
|
|
||||||
[:canvas#picker-detail {:class (stl/css :picker-detail) :width 256 :height 140}]]
|
|
||||||
[:& hsva-selector
|
|
||||||
{:color current-color
|
|
||||||
:disable-opacity disable-opacity
|
|
||||||
:on-change handle-change-color
|
|
||||||
:on-start-drag on-start-drag
|
|
||||||
:on-finish-drag on-finish-drag}])]]]
|
|
||||||
|
|
||||||
[:& color-inputs
|
[:& color-inputs
|
||||||
{:type (if (= @active-color-tab :hsva) :hsv :rgb)
|
{:type (if (= @active-color-tab :hsva) :hsv :rgb)
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
[app.main.ui.components.color-bullet :as cb]
|
[app.main.ui.components.color-bullet :as cb]
|
||||||
[app.main.ui.components.link-button :as lb]
|
[app.main.ui.components.link-button :as lb]
|
||||||
[app.main.ui.components.search-bar :refer [search-bar]]
|
[app.main.ui.components.search-bar :refer [search-bar]]
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
|
||||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||||
|
[app.main.ui.ds.tab-switcher :refer [tab-switcher*]]
|
||||||
[app.main.ui.hooks :as h]
|
[app.main.ui.hooks :as h]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.color :as uc]
|
[app.util.color :as uc]
|
||||||
|
@ -487,9 +487,6 @@
|
||||||
file-id (:id file)
|
file-id (:id file)
|
||||||
shared? (:is-shared file)
|
shared? (:is-shared file)
|
||||||
|
|
||||||
selected-tab* (mf/use-state starting-tab)
|
|
||||||
selected-tab (deref selected-tab*)
|
|
||||||
|
|
||||||
libraries (mf/deref refs/workspace-libraries)
|
libraries (mf/deref refs/workspace-libraries)
|
||||||
libraries (mf/with-memo [libraries]
|
libraries (mf/with-memo [libraries]
|
||||||
(d/removem (fn [[_ val]] (:is-indirect val)) libraries))
|
(d/removem (fn [[_ val]] (:is-indirect val)) libraries))
|
||||||
|
@ -498,9 +495,6 @@
|
||||||
shared-libraries
|
shared-libraries
|
||||||
(mf/deref refs/workspace-shared-files)
|
(mf/deref refs/workspace-shared-files)
|
||||||
|
|
||||||
on-tab-change
|
|
||||||
(mf/use-fn #(reset! selected-tab* %))
|
|
||||||
|
|
||||||
close-dialog-outside
|
close-dialog-outside
|
||||||
(mf/use-fn (fn [event]
|
(mf/use-fn (fn [event]
|
||||||
(when (= (dom/get-target event) (dom/get-current-target event))
|
(when (= (dom/get-target event) (dom/get-current-target event))
|
||||||
|
@ -509,7 +503,21 @@
|
||||||
close-dialog
|
close-dialog
|
||||||
(mf/use-fn (fn [_]
|
(mf/use-fn (fn [_]
|
||||||
(modal/hide!)
|
(modal/hide!)
|
||||||
(modal/disallow-click-outside!)))]
|
(modal/disallow-click-outside!)))
|
||||||
|
|
||||||
|
tabs
|
||||||
|
#js [#js {:label (tr "workspace.libraries.libraries")
|
||||||
|
:id "libraries"
|
||||||
|
:content (mf/html [:& libraries-tab {:file-id file-id
|
||||||
|
:shared? shared?
|
||||||
|
:linked-libraries libraries
|
||||||
|
:shared-libraries shared-libraries}])}
|
||||||
|
|
||||||
|
#js {:label (tr "workspace.libraries.updates")
|
||||||
|
:id "updates"
|
||||||
|
:content (mf/html [:& updates-tab {:file-id file-id
|
||||||
|
:file-data file-data
|
||||||
|
:libraries libraries}])}]]
|
||||||
|
|
||||||
(mf/with-effect [team-id]
|
(mf/with-effect [team-id]
|
||||||
(when team-id
|
(when team-id
|
||||||
|
@ -524,19 +532,9 @@
|
||||||
close-icon]
|
close-icon]
|
||||||
[:div {:class (stl/css :modal-title)}
|
[:div {:class (stl/css :modal-title)}
|
||||||
(tr "workspace.libraries.libraries")]
|
(tr "workspace.libraries.libraries")]
|
||||||
[:& tab-container
|
|
||||||
{:on-change-tab on-tab-change
|
[:> tab-switcher* {:tabs tabs
|
||||||
:selected selected-tab
|
:default-selected (dm/str starting-tab)}]]]))
|
||||||
:collapsable false}
|
|
||||||
[:& tab-element {:id :libraries :title (tr "workspace.libraries.libraries")}
|
|
||||||
[:& libraries-tab {:file-id file-id
|
|
||||||
:shared? shared?
|
|
||||||
:linked-libraries libraries
|
|
||||||
:shared-libraries shared-libraries}]]
|
|
||||||
[:& tab-element {:id :updates :title (tr "workspace.libraries.updates")}
|
|
||||||
[:& updates-tab {:file-id file-id
|
|
||||||
:file-data file-data
|
|
||||||
:libraries libraries}]]]]]))
|
|
||||||
|
|
||||||
(mf/defc v2-info-dialog
|
(mf/defc v2-info-dialog
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
|
|
|
@ -11,8 +11,9 @@
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
|
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||||
|
[app.main.ui.ds.tab-switcher :refer [tab-switcher*]]
|
||||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||||
[app.main.ui.workspace.comments :refer [comments-sidebar]]
|
[app.main.ui.workspace.comments :refer [comments-sidebar]]
|
||||||
[app.main.ui.workspace.left-header :refer [left-header]]
|
[app.main.ui.workspace.left-header :refer [left-header]]
|
||||||
|
@ -31,6 +32,17 @@
|
||||||
|
|
||||||
;; --- Left Sidebar (Component)
|
;; --- Left Sidebar (Component)
|
||||||
|
|
||||||
|
(mf/defc collapse-button
|
||||||
|
{::mf/wrap [mf/memo]
|
||||||
|
::mf/wrap-props false}
|
||||||
|
[{:keys [on-click] :as props}]
|
||||||
|
;; NOTE: This custom button may be replace by an action button when this variant is designed
|
||||||
|
[:button {:class (stl/css :collapse-sidebar-button)
|
||||||
|
:on-click on-click}
|
||||||
|
[:& icon* {:id "arrow"
|
||||||
|
:size "s"
|
||||||
|
:aria-label (tr "workspace.sidebar.collapse")}]])
|
||||||
|
|
||||||
(mf/defc left-sidebar
|
(mf/defc left-sidebar
|
||||||
{::mf/wrap [mf/memo]
|
{::mf/wrap [mf/memo]
|
||||||
::mf/wrap-props false}
|
::mf/wrap-props false}
|
||||||
|
@ -39,7 +51,6 @@
|
||||||
mode-inspect? (= options-mode :inspect)
|
mode-inspect? (= options-mode :inspect)
|
||||||
project (mf/deref refs/workspace-project)
|
project (mf/deref refs/workspace-project)
|
||||||
|
|
||||||
|
|
||||||
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
||||||
(contains? layout :assets) :assets)
|
(contains? layout :assets) :assets)
|
||||||
|
|
||||||
|
@ -60,7 +71,49 @@
|
||||||
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||||
|
|
||||||
on-tab-change
|
on-tab-change
|
||||||
(mf/use-fn #(st/emit! (dw/go-to-layout %)))]
|
(mf/use-fn #(st/emit! (dw/go-to-layout (keyword %))))
|
||||||
|
|
||||||
|
tabs (if ^boolean mode-inspect?
|
||||||
|
#js [#js {:label (tr "workspace.sidebar.layers")
|
||||||
|
:id "layers"
|
||||||
|
:content (mf/html [:article {:class (stl/css :layers-tab)
|
||||||
|
:style #js {"--height" (str size-pages "px")}}
|
||||||
|
|
||||||
|
[:& sitemap {:layout layout
|
||||||
|
:toggle-pages toggle-pages
|
||||||
|
:show-pages? @show-pages?
|
||||||
|
:size size-pages}]
|
||||||
|
|
||||||
|
(when @show-pages?
|
||||||
|
[:div {:class (stl/css :resize-area-horiz)
|
||||||
|
:on-pointer-down on-pointer-down-pages
|
||||||
|
:on-lost-pointer-capture on-lost-pointer-capture-pages
|
||||||
|
:on-pointer-move on-pointer-move-pages}])
|
||||||
|
|
||||||
|
[:& layers-toolbox {:size-parent size
|
||||||
|
:size size-pages}]])}]
|
||||||
|
#js [#js {:label (tr "workspace.sidebar.layers")
|
||||||
|
:id "layers"
|
||||||
|
:content (mf/html [:article {:class (stl/css :layers-tab)
|
||||||
|
:style #js {"--height" (str size-pages "px")}}
|
||||||
|
|
||||||
|
[:& sitemap {:layout layout
|
||||||
|
:toggle-pages toggle-pages
|
||||||
|
:show-pages? @show-pages?
|
||||||
|
:size size-pages}]
|
||||||
|
|
||||||
|
(when @show-pages?
|
||||||
|
[:div {:class (stl/css :resize-area-horiz)
|
||||||
|
:on-pointer-down on-pointer-down-pages
|
||||||
|
:on-lost-pointer-capture on-lost-pointer-capture-pages
|
||||||
|
:on-pointer-move on-pointer-move-pages}])
|
||||||
|
|
||||||
|
[:& layers-toolbox {:size-parent size
|
||||||
|
:size size-pages}]])}
|
||||||
|
#js {:label (tr "workspace.toolbar.assets")
|
||||||
|
:id "assets"
|
||||||
|
:content (mf/html [:& assets-toolbox {:size (- size 58)}])}])]
|
||||||
|
|
||||||
|
|
||||||
[:& (mf/provider muc/sidebar) {:value :left}
|
[:& (mf/provider muc/sidebar) {:value :left}
|
||||||
[:aside {:ref parent-ref
|
[:aside {:ref parent-ref
|
||||||
|
@ -89,36 +142,12 @@
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:div {:class (stl/css :settings-bar-content)}
|
[:div {:class (stl/css :settings-bar-content)}
|
||||||
[:& tab-container
|
[:> tab-switcher* {:tabs tabs
|
||||||
{:on-change-tab on-tab-change
|
:default-selected (dm/str section)
|
||||||
:selected section
|
:on-change-tab on-tab-change
|
||||||
:collapsable true
|
:class (stl/css :left-sidebar-tabs)
|
||||||
:handle-collapse handle-collapse
|
:action-button-position "start"
|
||||||
:header-class (stl/css :tab-spacing)}
|
:action-button (mf/html [:& collapse-button {:on-click handle-collapse}])}]])]]))
|
||||||
|
|
||||||
[:& tab-element {:id :layers
|
|
||||||
:title (tr "workspace.sidebar.layers")}
|
|
||||||
[:article {:class (stl/css :layers-tab)
|
|
||||||
:style #js {"--height" (str size-pages "px")}}
|
|
||||||
|
|
||||||
[:& sitemap {:layout layout
|
|
||||||
:toggle-pages toggle-pages
|
|
||||||
:show-pages? @show-pages?
|
|
||||||
:size size-pages}]
|
|
||||||
|
|
||||||
(when @show-pages?
|
|
||||||
[:div {:class (stl/css :resize-area-horiz)
|
|
||||||
:on-pointer-down on-pointer-down-pages
|
|
||||||
:on-lost-pointer-capture on-lost-pointer-capture-pages
|
|
||||||
:on-pointer-move on-pointer-move-pages}])
|
|
||||||
|
|
||||||
[:& layers-toolbox {:size-parent size
|
|
||||||
:size size-pages}]]]
|
|
||||||
|
|
||||||
(when-not ^boolean mode-inspect?
|
|
||||||
[:& tab-element {:id :assets
|
|
||||||
:title (tr "workspace.toolbar.assets")}
|
|
||||||
[:& assets-toolbox {:size (- size 58)}]])]])]]))
|
|
||||||
|
|
||||||
;; --- Right Sidebar (Component)
|
;; --- Right Sidebar (Component)
|
||||||
|
|
||||||
|
@ -132,9 +161,6 @@
|
||||||
is-history? (contains? layout :document-history)
|
is-history? (contains? layout :document-history)
|
||||||
is-inspect? (= section :inspect)
|
is-inspect? (= section :inspect)
|
||||||
|
|
||||||
;;expanded? (mf/deref refs/inspect-expanded)
|
|
||||||
;;prev-expanded? (hooks/use-previous expanded?)
|
|
||||||
|
|
||||||
current-section* (mf/use-state :info)
|
current-section* (mf/use-state :info)
|
||||||
current-section (deref current-section*)
|
current-section (deref current-section*)
|
||||||
|
|
||||||
|
@ -171,7 +197,7 @@
|
||||||
:id "right-sidebar-aside"
|
:id "right-sidebar-aside"
|
||||||
:data-testid "right-sidebar"
|
:data-testid "right-sidebar"
|
||||||
:data-size (str size)
|
:data-size (str size)
|
||||||
:style #js {"--width" (if can-be-expanded? (dm/str size "px") 276)}}
|
:style #js {"--width" (if can-be-expanded? (dm/str size "px") "276px")}}
|
||||||
(when can-be-expanded?
|
(when can-be-expanded?
|
||||||
[:div {:class (stl/css :resize-area)
|
[:div {:class (stl/css :resize-area)
|
||||||
:on-pointer-down on-pointer-down
|
:on-pointer-down on-pointer-down
|
||||||
|
|
|
@ -59,11 +59,11 @@ $width-settings-bar-max: $s-500;
|
||||||
|
|
||||||
.right-settings-bar {
|
.right-settings-bar {
|
||||||
grid-area: right-sidebar;
|
grid-area: right-sidebar;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
height: 100vh;
|
||||||
width: $width-settings-bar;
|
width: $width-settings-bar;
|
||||||
background-color: var(--panel-background-color);
|
background-color: var(--panel-background-color);
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
&.not-expand {
|
&.not-expand {
|
||||||
max-width: $width-settings-bar;
|
max-width: $width-settings-bar;
|
||||||
|
@ -89,3 +89,22 @@ $width-settings-bar-max: $s-500;
|
||||||
border-bottom: $s-2 solid var(--resize-area-border-color);
|
border-bottom: $s-2 solid var(--resize-area-border-color);
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.left-sidebar-tabs {
|
||||||
|
--tabs-nav-padding-inline-start: var(--sp-m);
|
||||||
|
--tabs-nav-padding-inline-end: var(--sp-m);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-sidebar-button {
|
||||||
|
--collapse-icon-color: var(--color-foreground-secondary);
|
||||||
|
@include flexCenter;
|
||||||
|
@include buttonStyle;
|
||||||
|
height: 100%;
|
||||||
|
width: $s-24;
|
||||||
|
border-radius: $br-5;
|
||||||
|
color: var(--collapse-icon-color);
|
||||||
|
transform: rotate(180deg);
|
||||||
|
&:hover {
|
||||||
|
--collapse-icon-color: var(--color-foreground-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
(:require
|
(:require
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@
|
||||||
:class (stl/css :collapsed-sidebar)}
|
:class (stl/css :collapsed-sidebar)}
|
||||||
[:div {:class (stl/css :collapsed-title)}
|
[:div {:class (stl/css :collapsed-title)}
|
||||||
[:button {:class (stl/css :collapsed-button)
|
[:button {:class (stl/css :collapsed-button)
|
||||||
:on-click on-click
|
:title (tr "workspace.sidebar.expand")
|
||||||
:aria-label (tr "workspace.sidebar.expand")}
|
:on-click on-click}
|
||||||
i/arrow]]]))
|
[:& icon* {:id "arrow"
|
||||||
|
:size "s"
|
||||||
|
:aria-label (tr "workspace.sidebar.expand")}]]]]))
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
padding: $s-4;
|
padding: $s-4;
|
||||||
border-radius: $br-8;
|
border-radius: $br-8;
|
||||||
background: var(--color-background-primary);
|
background: var(--color-background-primary);
|
||||||
|
margin-inline-start: var(--sp-m);
|
||||||
}
|
}
|
||||||
.collapsed-title {
|
.collapsed-title {
|
||||||
@include flexCenter;
|
@include flexCenter;
|
||||||
height: $s-32;
|
height: $s-36;
|
||||||
width: $s-24;
|
width: $s-24;
|
||||||
border-radius: $br-8;
|
border-radius: $br-8;
|
||||||
background: var(--color-background-secondary);
|
background: var(--color-background-secondary);
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
[app.main.data.workspace :as udw]
|
[app.main.data.workspace :as udw]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
|
|
||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
|
[app.main.ui.ds.tab-switcher :refer [tab-switcher*]]
|
||||||
[app.main.ui.viewer.inspect.right-sidebar :as hrs]
|
[app.main.ui.viewer.inspect.right-sidebar :as hrs]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
|
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
|
||||||
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]]
|
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]]
|
||||||
|
@ -72,102 +72,117 @@
|
||||||
(when (= (:type panel) :component-swap)
|
(when (= (:type panel) :component-swap)
|
||||||
[:& component-menu {:shapes (:shapes panel) :swap-opened? true}]))
|
[:& component-menu {:shapes (:shapes panel) :swap-opened? true}]))
|
||||||
|
|
||||||
|
(mf/defc design-menu
|
||||||
|
{::mf/wrap [mf/memo]}
|
||||||
|
[{:keys [selected objects page-id file-id selected-shapes shapes-with-children]}]
|
||||||
|
(let [sp-panel (mf/deref refs/specialized-panel)
|
||||||
|
drawing (mf/deref refs/workspace-drawing)
|
||||||
|
shared-libs (mf/deref refs/workspace-libraries)
|
||||||
|
edition (mf/deref refs/selected-edition)
|
||||||
|
edit-grid? (ctl/grid-layout? objects edition)
|
||||||
|
grid-edition (mf/deref refs/workspace-grid-edition)
|
||||||
|
selected-cells (->> (dm/get-in grid-edition [edition :selected])
|
||||||
|
(map #(dm/get-in objects [edition :layout-grid-cells %])))]
|
||||||
|
|
||||||
|
[:div {:class (stl/css :element-options :design-options)}
|
||||||
|
[:& align-options]
|
||||||
|
[:& bool-options]
|
||||||
|
|
||||||
|
(cond
|
||||||
|
(and edit-grid? (d/not-empty? selected-cells))
|
||||||
|
[:& grid-cell/options
|
||||||
|
{:shape (get objects edition)
|
||||||
|
:cells selected-cells}]
|
||||||
|
|
||||||
|
edit-grid?
|
||||||
|
[:& layout-container/grid-layout-edition
|
||||||
|
{:ids [edition]
|
||||||
|
:values (get objects edition)}]
|
||||||
|
|
||||||
|
(not (nil? sp-panel))
|
||||||
|
[:& specialized-panel {:panel sp-panel}]
|
||||||
|
|
||||||
|
(d/not-empty? drawing)
|
||||||
|
[:& shape-options
|
||||||
|
{:shape (:object drawing)
|
||||||
|
:page-id page-id
|
||||||
|
:file-id file-id
|
||||||
|
:shared-libs shared-libs}]
|
||||||
|
|
||||||
|
(= 0 (count selected))
|
||||||
|
[:& page/options]
|
||||||
|
|
||||||
|
(= 1 (count selected))
|
||||||
|
[:& shape-options
|
||||||
|
{:shape (first selected-shapes)
|
||||||
|
:page-id page-id
|
||||||
|
:file-id file-id
|
||||||
|
:shared-libs shared-libs
|
||||||
|
:shapes-with-children shapes-with-children}]
|
||||||
|
|
||||||
|
:else
|
||||||
|
[:& multiple/options
|
||||||
|
{:shapes-with-children shapes-with-children
|
||||||
|
:shapes selected-shapes
|
||||||
|
:page-id page-id
|
||||||
|
:file-id file-id
|
||||||
|
:shared-libs shared-libs}])]))
|
||||||
|
|
||||||
(mf/defc options-content
|
(mf/defc options-content
|
||||||
{::mf/memo true
|
{::mf/memo true
|
||||||
::mf/props :obj}
|
::mf/props :obj}
|
||||||
[{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}]
|
[{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}]
|
||||||
(let [drawing (mf/deref refs/workspace-drawing)
|
(let [objects (mf/deref refs/workspace-page-objects)
|
||||||
objects (mf/deref refs/workspace-page-objects)
|
|
||||||
shared-libs (mf/deref refs/workspace-libraries)
|
|
||||||
edition (mf/deref refs/selected-edition)
|
|
||||||
grid-edition (mf/deref refs/workspace-grid-edition)
|
|
||||||
sp-panel (mf/deref refs/specialized-panel)
|
|
||||||
|
|
||||||
selected-shapes (into [] (keep (d/getf objects)) selected)
|
selected-shapes (into [] (keep (d/getf objects)) selected)
|
||||||
first-selected-shape (first selected-shapes)
|
first-selected-shape (first selected-shapes)
|
||||||
shape-parent-frame (cfh/get-frame objects (:frame-id first-selected-shape))
|
shape-parent-frame (cfh/get-frame objects (:frame-id first-selected-shape))
|
||||||
|
|
||||||
edit-grid? (ctl/grid-layout? objects edition)
|
|
||||||
selected-cells (->> (dm/get-in grid-edition [edition :selected])
|
|
||||||
(map #(dm/get-in objects [edition :layout-grid-cells %])))
|
|
||||||
|
|
||||||
on-change-tab
|
on-change-tab
|
||||||
(fn [options-mode]
|
(fn [options-mode]
|
||||||
(st/emit! (udw/set-options-mode options-mode))
|
(let [options-mode (keyword options-mode)]
|
||||||
(if (= options-mode :inspect)
|
(st/emit! (udw/set-options-mode options-mode))
|
||||||
(st/emit! :interrupt (udw/set-workspace-read-only true))
|
(if (= options-mode :inspect)
|
||||||
(st/emit! :interrupt (udw/set-workspace-read-only false))))]
|
(st/emit! :interrupt (udw/set-workspace-read-only true))
|
||||||
|
(st/emit! :interrupt (udw/set-workspace-read-only false)))))
|
||||||
|
|
||||||
|
design-content (mf/html [:& design-menu {:selected selected
|
||||||
|
:objects objects
|
||||||
|
:page-id page-id
|
||||||
|
:file-id file-id
|
||||||
|
:selected-shapes selected-shapes
|
||||||
|
:shapes-with-children shapes-with-children}])
|
||||||
|
|
||||||
|
inspect-content (mf/html [:div {:class (stl/css :element-options :inspect-options)}
|
||||||
|
[:& hrs/right-sidebar {:page-id page-id
|
||||||
|
:objects objects
|
||||||
|
:file-id file-id
|
||||||
|
:frame shape-parent-frame
|
||||||
|
:shapes selected-shapes
|
||||||
|
:on-change-section on-change-section
|
||||||
|
:on-expand on-expand
|
||||||
|
:from :workspace}]])
|
||||||
|
|
||||||
|
interactions-content (mf/html [:div {:class (stl/css :element-options :interaction-options)}
|
||||||
|
[:& interactions-menu {:shape (first shapes)}]])
|
||||||
|
tabs
|
||||||
|
#js [#js {:label (tr "workspace.options.design")
|
||||||
|
:id "design"
|
||||||
|
:content design-content}
|
||||||
|
|
||||||
|
#js {:label (tr "workspace.options.prototype")
|
||||||
|
:id "prototype"
|
||||||
|
:content interactions-content}
|
||||||
|
|
||||||
|
#js {:label (tr "workspace.options.inspect")
|
||||||
|
:id "inspect"
|
||||||
|
:content inspect-content}]]
|
||||||
|
|
||||||
[:div {:class (stl/css :tool-window)}
|
[:div {:class (stl/css :tool-window)}
|
||||||
[:& tab-container
|
[:> tab-switcher* {:tabs tabs
|
||||||
{:on-change-tab on-change-tab
|
:default-selected "info"
|
||||||
:selected section
|
:on-change-tab on-change-tab
|
||||||
:collapsable false
|
:class (stl/css :options-tab-switcher)}]]))
|
||||||
:content-class (stl/css-case
|
|
||||||
:content-class true
|
|
||||||
:inspect (= section :inspect))
|
|
||||||
:header-class (stl/css :tab-spacing)}
|
|
||||||
[:& tab-element {:id :design
|
|
||||||
:title (tr "workspace.options.design")}
|
|
||||||
[:div {:class (stl/css :element-options)}
|
|
||||||
[:& align-options]
|
|
||||||
[:& bool-options]
|
|
||||||
|
|
||||||
(cond
|
|
||||||
(and edit-grid? (d/not-empty? selected-cells))
|
|
||||||
[:& grid-cell/options
|
|
||||||
{:shape (get objects edition)
|
|
||||||
:cells selected-cells}]
|
|
||||||
|
|
||||||
edit-grid?
|
|
||||||
[:& layout-container/grid-layout-edition
|
|
||||||
{:ids [edition]
|
|
||||||
:values (get objects edition)}]
|
|
||||||
|
|
||||||
(not (nil? sp-panel))
|
|
||||||
[:& specialized-panel {:panel sp-panel}]
|
|
||||||
|
|
||||||
(d/not-empty? drawing)
|
|
||||||
[:& shape-options
|
|
||||||
{:shape (:object drawing)
|
|
||||||
:page-id page-id
|
|
||||||
:file-id file-id
|
|
||||||
:shared-libs shared-libs}]
|
|
||||||
|
|
||||||
(= 0 (count selected))
|
|
||||||
[:& page/options]
|
|
||||||
|
|
||||||
(= 1 (count selected))
|
|
||||||
[:& shape-options
|
|
||||||
{:shape (first selected-shapes)
|
|
||||||
:page-id page-id
|
|
||||||
:file-id file-id
|
|
||||||
:shared-libs shared-libs
|
|
||||||
:shapes-with-children shapes-with-children}]
|
|
||||||
|
|
||||||
:else
|
|
||||||
[:& multiple/options
|
|
||||||
{:shapes-with-children shapes-with-children
|
|
||||||
:shapes selected-shapes
|
|
||||||
:page-id page-id
|
|
||||||
:file-id file-id
|
|
||||||
:shared-libs shared-libs}])]]
|
|
||||||
[:& tab-element {:id :prototype
|
|
||||||
:title (tr "workspace.options.prototype")}
|
|
||||||
[:div {:class (stl/css :element-options)}
|
|
||||||
[:& interactions-menu {:shape (first shapes)}]]]
|
|
||||||
[:& tab-element {:id :inspect
|
|
||||||
:title (tr "workspace.options.inspect")}
|
|
||||||
[:div {:class (stl/css :element-options)}
|
|
||||||
[:& hrs/right-sidebar {:page-id page-id
|
|
||||||
:objects objects
|
|
||||||
:file-id file-id
|
|
||||||
:frame shape-parent-frame
|
|
||||||
:shapes selected-shapes
|
|
||||||
:on-change-section on-change-section
|
|
||||||
:on-expand on-expand
|
|
||||||
:from :workspace}]]]]]))
|
|
||||||
|
|
||||||
;; TODO: this need optimizations, selected-objects and
|
;; TODO: this need optimizations, selected-objects and
|
||||||
;; selected-objects-with-children are derefed always but they only
|
;; selected-objects-with-children are derefed always but they only
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
|
|
||||||
.tool-window {
|
.tool-window {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-left: $s-12;
|
padding-left: $s-12;
|
||||||
|
@ -27,14 +26,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.element-options {
|
.element-options {
|
||||||
width: 100%;
|
display: grid;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $s-8;
|
gap: $s-8;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - $s-80);
|
||||||
padding-top: $s-8;
|
padding-top: $s-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.design-options,
|
||||||
|
.interaction-options {
|
||||||
|
overflow: auto;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
.inspect {
|
.inspect {
|
||||||
scrollbar-gutter: unset;
|
scrollbar-gutter: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options-tab-switcher {
|
||||||
|
--tabs-nav-padding-inline-end: 12px;
|
||||||
|
}
|
||||||
|
|
|
@ -8,44 +8,7 @@
|
||||||
|
|
||||||
.stroke-data {
|
.stroke-data {
|
||||||
@include flexColumn;
|
@include flexColumn;
|
||||||
.stroke-options {
|
|
||||||
@include flexRow;
|
|
||||||
.stroke-width-input-element {
|
|
||||||
@extend .input-element;
|
|
||||||
@include bodySmallTypography;
|
|
||||||
width: $s-60;
|
|
||||||
}
|
|
||||||
.select-wrapper {
|
|
||||||
width: $s-124;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stroke-caps-options {
|
|
||||||
@include flexRow;
|
|
||||||
.cap-select {
|
|
||||||
width: $s-124;
|
|
||||||
}
|
|
||||||
.stroke-cap-dropdown,
|
|
||||||
.stroke-cap-dropdown-start {
|
|
||||||
min-width: $s-124;
|
|
||||||
width: fit-content;
|
|
||||||
max-width: $s-252;
|
|
||||||
right: 0;
|
|
||||||
left: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stroke-cap-dropdown-start {
|
|
||||||
left: 0;
|
|
||||||
right: unset;
|
|
||||||
}
|
|
||||||
.swap-caps-btn {
|
|
||||||
@extend .button-secondary;
|
|
||||||
height: $s-32;
|
|
||||||
width: $s-28;
|
|
||||||
svg {
|
|
||||||
@extend .button-icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.dnd-over-top {
|
&.dnd-over-top {
|
||||||
border-top: $s-1 solid var(--layer-row-foreground-color-drag);
|
border-top: $s-1 solid var(--layer-row-foreground-color-drag);
|
||||||
}
|
}
|
||||||
|
@ -53,3 +16,42 @@
|
||||||
border-bottom: $s-1 solid var(--layer-row-foreground-color-drag);
|
border-bottom: $s-1 solid var(--layer-row-foreground-color-drag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stroke-options {
|
||||||
|
@include flexRow;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 2fr;
|
||||||
|
|
||||||
|
.stroke-width-input-element {
|
||||||
|
@extend .input-element;
|
||||||
|
@include bodySmallTypography;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.stroke-caps-options {
|
||||||
|
@include flexRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cap-select {
|
||||||
|
width: $s-124;
|
||||||
|
}
|
||||||
|
.stroke-cap-dropdown,
|
||||||
|
.stroke-cap-dropdown-start {
|
||||||
|
min-width: $s-124;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: $s-252;
|
||||||
|
right: 0;
|
||||||
|
left: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stroke-cap-dropdown-start {
|
||||||
|
left: 0;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
.swap-caps-btn {
|
||||||
|
@extend .button-secondary;
|
||||||
|
height: $s-32;
|
||||||
|
width: $s-28;
|
||||||
|
svg {
|
||||||
|
@extend .button-icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
(ns app.util.array
|
(ns app.util.array
|
||||||
"A collection of helpers for work with javascript arrays."
|
"A collection of helpers for work with javascript arrays."
|
||||||
(:refer-clojure :exclude [conj! conj filter]))
|
(:refer-clojure :exclude [conj! conj filter map reduce find])
|
||||||
|
(:require
|
||||||
|
[cljs.core :as c]))
|
||||||
|
|
||||||
(defn conj
|
(defn conj
|
||||||
"A conj like function for js arrays."
|
"A conj like function for js arrays."
|
||||||
|
@ -49,3 +51,19 @@
|
||||||
"A specific filter for js arrays."
|
"A specific filter for js arrays."
|
||||||
[pred ^js/Array o]
|
[pred ^js/Array o]
|
||||||
(.filter o pred))
|
(.filter o pred))
|
||||||
|
|
||||||
|
(defn map
|
||||||
|
[f a]
|
||||||
|
(.map ^js/Array a f))
|
||||||
|
|
||||||
|
(defn reduce
|
||||||
|
[f init val]
|
||||||
|
(.reduce ^js/Array val f init))
|
||||||
|
|
||||||
|
(defn find-index
|
||||||
|
[f v]
|
||||||
|
(.findIndex ^js/Array v f))
|
||||||
|
|
||||||
|
(defn find
|
||||||
|
[f v]
|
||||||
|
(.find ^js/Array v f))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue