mirror of
https://github.com/penpot/penpot.git
synced 2025-04-16 00:41:25 -05: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
frontend
|
@ -20,8 +20,8 @@
|
|||
:git/url "https://github.com/funcool/beicon.git"}
|
||||
|
||||
funcool/rumext
|
||||
{:git/tag "v2.12"
|
||||
:git/sha "ab819f5"
|
||||
{:git/tag "v2.13"
|
||||
:git/sha "dc8e1e5"
|
||||
:git/url "https://github.com/funcool/rumext.git"}
|
||||
|
||||
instaparse/instaparse {:mvn/version "1.5.0"}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[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.frame-preview :as frame-preview]
|
||||
[app.main.ui.icons :as i]
|
||||
|
@ -169,12 +168,6 @@
|
|||
:layout-name layout
|
||||
:key file-id}]])
|
||||
|
||||
|
||||
:debug-components-preview
|
||||
[:div.debug-preview
|
||||
[:h1 "Components preview"]
|
||||
[:& cm/components-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.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
|
||||
[app.util.array :as array]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
|
@ -19,45 +19,50 @@
|
|||
(mf/defc tab*
|
||||
{::mf/props :obj
|
||||
::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
|
||||
:selected selected)
|
||||
props (mf/spread-props props {:class class
|
||||
:role "tab"
|
||||
:aria-selected selected
|
||||
:title (or label aria-label)
|
||||
:tab-index (if selected nil -1)
|
||||
:ref tab-ref
|
||||
:data-id id})]
|
||||
props (mf/spread-props props
|
||||
{:class class
|
||||
:role "tab"
|
||||
:aria-selected selected
|
||||
:title (or label aria-label)
|
||||
:tab-index (if selected nil -1)
|
||||
:ref (fn [node]
|
||||
(on-ref node id))
|
||||
:data-id id
|
||||
;; This prop is to be used for accessibility purposes only.
|
||||
:id id})]
|
||||
|
||||
[:> "li" {}
|
||||
[:> "button" props
|
||||
(when icon
|
||||
[:li
|
||||
[:> :button props
|
||||
(when (some? icon)
|
||||
[:> icon*
|
||||
{:id icon
|
||||
:aria-hidden (when label true)
|
||||
:aria-label (when (not label) aria-label)}])
|
||||
(when label
|
||||
(when (string? label)
|
||||
[: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/props :obj
|
||||
::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
|
||||
:tab-nav-start (= "start" button-position)
|
||||
:tab-nav-end (= "end" button-position))
|
||||
props (mf/spread-props props {:class (stl/css :tab-list)
|
||||
:role "tablist"
|
||||
:aria-orientation "horizontal"})]
|
||||
[:> "nav" {:class class}
|
||||
props (mf/spread-props props
|
||||
{:class (stl/css :tab-list)
|
||||
:role "tablist"
|
||||
:aria-orientation "horizontal"})]
|
||||
[:nav {:class class}
|
||||
(when (= button-position "start")
|
||||
action-button)
|
||||
|
||||
[:> "ul" props
|
||||
(for [[index element] (map-indexed vector tabs)]
|
||||
(for [element ^js tabs]
|
||||
(let [icon (obj/get element "icon")
|
||||
label (obj/get element "label")
|
||||
aria-label (obj/get element "aria-label")
|
||||
|
@ -67,94 +72,129 @@
|
|||
:key (dm/str "tab-" id)
|
||||
:label label
|
||||
:aria-label aria-label
|
||||
:selected (= index selected)
|
||||
:selected (= id selected)
|
||||
:on-click on-click
|
||||
:id id
|
||||
:tab-ref (nth tabs-refs index)}]))]
|
||||
:on-ref on-ref
|
||||
:id id}]))]
|
||||
|
||||
(when (= button-position "end")
|
||||
action-button)]))
|
||||
|
||||
(mf/defc tab-panel*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [children name] :rest props}]
|
||||
(let [props (mf/spread-props props {:class (stl/css :tab-panel)
|
||||
:aria-labelledby name
|
||||
:role "tabpanel"})]
|
||||
[:> "section" props
|
||||
children]))
|
||||
(defn- get-tab
|
||||
[tabs id]
|
||||
(or (array/find #(= id (obj/get % "id")) tabs)
|
||||
(aget tabs 0)))
|
||||
|
||||
(defn- valid-tabs?
|
||||
[tabs]
|
||||
(every? (fn [tab]
|
||||
(let [icon (obj/get tab "icon")
|
||||
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)))
|
||||
(defn- get-selected-tab-id
|
||||
[tabs default]
|
||||
(let [tab (get-tab tabs default)]
|
||||
(obj/get tab "id")))
|
||||
|
||||
(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]
|
||||
(or (nil? position) (and (contains? positions position) (some? button))))
|
||||
(def ^:private schema:tab-switcher
|
||||
[: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/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}]
|
||||
;; TODO: Use a schema to assert the tabs prop -> https://tree.taiga.io/project/penpot/task/8521
|
||||
(assert (valid-tabs? tabs) "unexpected props for tab-switcher")
|
||||
(assert (valid-button-position? action-button-position action-button) "invalid action-button-position")
|
||||
(let [tab-ids (mapv #(obj/get % "id") tabs)
|
||||
(let [selected* (mf/use-state #(get-selected-tab-id tabs default-selected))
|
||||
selected (deref selected*)
|
||||
|
||||
active-tab-index* (mf/use-state (or (d/index-of tab-ids default-selected) 0))
|
||||
active-tab-index (deref active-tab-index*)
|
||||
tabs-nodes-refs (mf/use-ref nil)
|
||||
tabs-ref (mf/use-ref nil)
|
||||
|
||||
tabs-refs (mapv (fn [_] (mf/use-ref)) tabs)
|
||||
|
||||
active-tab (nth tabs active-tab-index)
|
||||
panel-content (obj/get active-tab "content")
|
||||
|
||||
handle-click
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps on-change-tab tab-ids)
|
||||
(mf/deps on-change-tab)
|
||||
(fn [event]
|
||||
(let [id (dom/get-data (dom/get-current-target event) "id")
|
||||
index (d/index-of tab-ids id)]
|
||||
(reset! active-tab-index* index)
|
||||
(let [node (dom/get-current-target event)
|
||||
id (dom/get-data node "id")]
|
||||
(reset! selected* id)
|
||||
|
||||
(when (fn? on-change-tab)
|
||||
(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
|
||||
(mf/use-fn
|
||||
(mf/deps tabs-refs active-tab-index)
|
||||
(mf/deps selected)
|
||||
(fn [event]
|
||||
(let [len (count tabs-refs)
|
||||
index (cond
|
||||
(kbd/home? event) 0
|
||||
(kbd/left-arrow? event) (mod (- active-tab-index 1) len)
|
||||
(kbd/right-arrow? event) (mod (+ active-tab-index 1) len))]
|
||||
(when index
|
||||
(reset! active-tab-index* index)
|
||||
(dom/focus! (mf/ref-val (nth tabs-refs index)))))))
|
||||
(let [tabs (mf/ref-val tabs-ref)
|
||||
len (alength tabs)
|
||||
sel? #(= selected (obj/get % "id"))
|
||||
id (cond
|
||||
(kbd/home? event)
|
||||
(let [tab (aget tabs 0)]
|
||||
(obj/get tab "id"))
|
||||
|
||||
(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))
|
||||
|
||||
props (mf/spread-props props {:class class
|
||||
:on-key-down on-key-down})]
|
||||
|
||||
[:> "article" props
|
||||
[:> "div" {:class (stl/css :padding-wrapper)}
|
||||
(mf/with-effect [tabs]
|
||||
(mf/set-ref-val! tabs-ref tabs))
|
||||
|
||||
[:> :article props
|
||||
[:div {:class (stl/css :padding-wrapper)}
|
||||
[:> tab-nav* {:button-position action-button-position
|
||||
:action-button action-button
|
||||
:tabs tabs
|
||||
:selected active-tab-index
|
||||
:on-click handle-click
|
||||
:tabs-refs tabs-refs}]]
|
||||
|
||||
[:> tab-panel* {:tab-index 0}
|
||||
panel-content]]))
|
||||
:on-ref on-ref
|
||||
:selected selected
|
||||
:on-click on-click}]]
|
||||
|
||||
(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 {
|
||||
padding-inline: var(--sp-xxs);
|
||||
}
|
||||
|
||||
.tab-panel {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -60,8 +60,6 @@
|
|||
(when *assert*
|
||||
["/debug/icons-preview" :debug-icons-preview])
|
||||
|
||||
["/debug/components-preview" :debug-components-preview]
|
||||
|
||||
;; Used for export
|
||||
["/render-sprite/:file-id" :render-sprite]
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
[{:keys [local file page frame index viewer-pagination size share-id]}]
|
||||
(let [inspect-svg-container-ref (mf/use-ref nil)
|
||||
current-section* (mf/use-state :info)
|
||||
current-section (deref current-section*)
|
||||
current-section (deref current-section*)
|
||||
|
||||
can-be-expanded? (= current-section :code)
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
content (when (= (count shapes) 1)
|
||||
(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)]
|
||||
[:> (case option
|
||||
:geometry geometry-panel
|
||||
|
|
|
@ -11,6 +11,13 @@
|
|||
flex-direction: column;
|
||||
gap: $s-16;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: calc(100vh - #{$s-128}); // TODO: Fix this hardcoded value
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
height: calc(100vh - #{$s-128}); // TODO: Fix this hardcoded value
|
||||
overflow: hidden;
|
||||
padding-bottom: $s-16;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.common.types.component :as ctk]
|
||||
[app.main.refs :as refs]
|
||||
[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.viewer.inspect.attributes :refer [attributes]]
|
||||
[app.main.ui.viewer.inspect.code :refer [code]]
|
||||
|
@ -61,9 +61,9 @@
|
|||
(mf/use-fn
|
||||
(mf/deps from on-change-section)
|
||||
(fn [new-section]
|
||||
(reset! section new-section)
|
||||
(reset! section (keyword new-section))
|
||||
(when on-change-section
|
||||
(on-change-section new-section))))
|
||||
(on-change-section (keyword new-section)))))
|
||||
|
||||
handle-expand
|
||||
(mf/use-fn
|
||||
|
@ -74,7 +74,33 @@
|
|||
navigate-to-help
|
||||
(mf/use-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/deps shapes handle-change-tab)
|
||||
|
@ -108,25 +134,11 @@
|
|||
;; (tr "inspect.tabs.code.selected.text")
|
||||
[:span {:class (stl/css :layer-title)} (:name first-shape)]])]
|
||||
[: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")}
|
||||
[:& code {:frame frame
|
||||
:shapes shapes
|
||||
:on-expand handle-expand
|
||||
:from from}]]]]]
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default-selected "info"
|
||||
:on-change-tab handle-change-tab
|
||||
:class (stl/css :viewer-tab-switcher)}]]]
|
||||
[:div {:class (stl/css :empty)}
|
||||
[:div {:class (stl/css :code-info)}
|
||||
[:span {:class (stl/css :placeholder-icon)}
|
||||
|
|
|
@ -97,10 +97,6 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
margin-right: $s-12;
|
||||
.viewer-tab-switcher {
|
||||
--tabs-nav-padding-inline-end: var(--sp-m);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[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.workspace.colorpicker.color-inputs :refer [color-inputs]]
|
||||
[app.main.ui.workspace.colorpicker.gradients :refer [gradients]]
|
||||
|
@ -208,7 +209,50 @@
|
|||
[{:value :linear-gradient :label (tr "media.linear")}
|
||||
{:value :radial-gradient :label (tr "media.radial")}])
|
||||
(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
|
||||
(mf/with-effect []
|
||||
|
@ -306,46 +350,9 @@
|
|||
:on-selected on-fill-image-selected}]]])
|
||||
[:*
|
||||
[:div {:class (stl/css :colorpicker-tabs)}
|
||||
[:& tab-container
|
||||
{:on-change-tab on-change-tab
|
||||
:selected @active-color-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}])]]]
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default-selected "ramp"
|
||||
:on-change-tab on-change-tab}]]
|
||||
|
||||
[:& color-inputs
|
||||
{:type (if (= @active-color-tab :hsva) :hsv :rgb)
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
[app.main.ui.components.color-bullet :as cb]
|
||||
[app.main.ui.components.link-button :as lb]
|
||||
[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.ds.tab-switcher :refer [tab-switcher*]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.color :as uc]
|
||||
|
@ -487,9 +487,6 @@
|
|||
file-id (:id 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/with-memo [libraries]
|
||||
(d/removem (fn [[_ val]] (:is-indirect val)) libraries))
|
||||
|
@ -498,9 +495,6 @@
|
|||
shared-libraries
|
||||
(mf/deref refs/workspace-shared-files)
|
||||
|
||||
on-tab-change
|
||||
(mf/use-fn #(reset! selected-tab* %))
|
||||
|
||||
close-dialog-outside
|
||||
(mf/use-fn (fn [event]
|
||||
(when (= (dom/get-target event) (dom/get-current-target event))
|
||||
|
@ -509,7 +503,21 @@
|
|||
close-dialog
|
||||
(mf/use-fn (fn [_]
|
||||
(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]
|
||||
(when team-id
|
||||
|
@ -524,19 +532,9 @@
|
|||
close-icon]
|
||||
[:div {:class (stl/css :modal-title)}
|
||||
(tr "workspace.libraries.libraries")]
|
||||
[:& tab-container
|
||||
{:on-change-tab on-tab-change
|
||||
:selected selected-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}]]]]]))
|
||||
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default-selected (dm/str starting-tab)}]]]))
|
||||
|
||||
(mf/defc v2-info-dialog
|
||||
{::mf/register modal/components
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[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.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.workspace.comments :refer [comments-sidebar]]
|
||||
[app.main.ui.workspace.left-header :refer [left-header]]
|
||||
|
@ -31,6 +32,17 @@
|
|||
|
||||
;; --- 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/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
|
@ -39,7 +51,6 @@
|
|||
mode-inspect? (= options-mode :inspect)
|
||||
project (mf/deref refs/workspace-project)
|
||||
|
||||
|
||||
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
||||
(contains? layout :assets) :assets)
|
||||
|
||||
|
@ -60,7 +71,49 @@
|
|||
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
|
||||
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}
|
||||
[:aside {:ref parent-ref
|
||||
|
@ -89,36 +142,12 @@
|
|||
|
||||
:else
|
||||
[:div {:class (stl/css :settings-bar-content)}
|
||||
[:& tab-container
|
||||
{:on-change-tab on-tab-change
|
||||
:selected section
|
||||
:collapsable true
|
||||
:handle-collapse handle-collapse
|
||||
:header-class (stl/css :tab-spacing)}
|
||||
|
||||
[:& 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)}]])]])]]))
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default-selected (dm/str section)
|
||||
:on-change-tab on-tab-change
|
||||
:class (stl/css :left-sidebar-tabs)
|
||||
:action-button-position "start"
|
||||
:action-button (mf/html [:& collapse-button {:on-click handle-collapse}])}]])]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
|
@ -132,9 +161,6 @@
|
|||
is-history? (contains? layout :document-history)
|
||||
is-inspect? (= section :inspect)
|
||||
|
||||
;;expanded? (mf/deref refs/inspect-expanded)
|
||||
;;prev-expanded? (hooks/use-previous expanded?)
|
||||
|
||||
current-section* (mf/use-state :info)
|
||||
current-section (deref current-section*)
|
||||
|
||||
|
@ -171,7 +197,7 @@
|
|||
:id "right-sidebar-aside"
|
||||
:data-testid "right-sidebar"
|
||||
: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?
|
||||
[:div {:class (stl/css :resize-area)
|
||||
:on-pointer-down on-pointer-down
|
||||
|
|
|
@ -59,11 +59,11 @@ $width-settings-bar-max: $s-500;
|
|||
|
||||
.right-settings-bar {
|
||||
grid-area: right-sidebar;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
height: 100vh;
|
||||
width: $width-settings-bar;
|
||||
background-color: var(--panel-background-color);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 0;
|
||||
&.not-expand {
|
||||
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);
|
||||
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
|
||||
[app.main.data.workspace :as dw]
|
||||
[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]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -22,6 +22,8 @@
|
|||
:class (stl/css :collapsed-sidebar)}
|
||||
[:div {:class (stl/css :collapsed-title)}
|
||||
[:button {:class (stl/css :collapsed-button)
|
||||
:on-click on-click
|
||||
:aria-label (tr "workspace.sidebar.expand")}
|
||||
i/arrow]]]))
|
||||
:title (tr "workspace.sidebar.expand")
|
||||
:on-click on-click}
|
||||
[:& icon* {:id "arrow"
|
||||
:size "s"
|
||||
:aria-label (tr "workspace.sidebar.expand")}]]]]))
|
||||
|
|
|
@ -14,10 +14,11 @@
|
|||
padding: $s-4;
|
||||
border-radius: $br-8;
|
||||
background: var(--color-background-primary);
|
||||
margin-inline-start: var(--sp-m);
|
||||
}
|
||||
.collapsed-title {
|
||||
@include flexCenter;
|
||||
height: $s-32;
|
||||
height: $s-36;
|
||||
width: $s-24;
|
||||
border-radius: $br-8;
|
||||
background: var(--color-background-secondary);
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
[app.main.data.workspace :as udw]
|
||||
[app.main.refs :as refs]
|
||||
[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.ds.tab-switcher :refer [tab-switcher*]]
|
||||
[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.bool :refer [bool-options]]
|
||||
|
@ -72,102 +72,117 @@
|
|||
(when (= (:type panel) :component-swap)
|
||||
[:& 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/memo true
|
||||
::mf/props :obj}
|
||||
[{:keys [selected section shapes shapes-with-children page-id file-id on-change-section on-expand]}]
|
||||
(let [drawing (mf/deref refs/workspace-drawing)
|
||||
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)
|
||||
[{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}]
|
||||
(let [objects (mf/deref refs/workspace-page-objects)
|
||||
|
||||
selected-shapes (into [] (keep (d/getf objects)) selected)
|
||||
first-selected-shape (first selected-shapes)
|
||||
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
|
||||
(fn [options-mode]
|
||||
(st/emit! (udw/set-options-mode options-mode))
|
||||
(if (= options-mode :inspect)
|
||||
(st/emit! :interrupt (udw/set-workspace-read-only true))
|
||||
(st/emit! :interrupt (udw/set-workspace-read-only false))))]
|
||||
(let [options-mode (keyword options-mode)]
|
||||
(st/emit! (udw/set-options-mode options-mode))
|
||||
(if (= options-mode :inspect)
|
||||
(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)}
|
||||
[:& tab-container
|
||||
{:on-change-tab on-change-tab
|
||||
:selected section
|
||||
:collapsable false
|
||||
: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}]]]]]))
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
:default-selected "info"
|
||||
:on-change-tab on-change-tab
|
||||
:class (stl/css :options-tab-switcher)}]]))
|
||||
|
||||
;; TODO: this need optimizations, selected-objects and
|
||||
;; selected-objects-with-children are derefed always but they only
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
.tool-window {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-left: $s-12;
|
||||
|
@ -27,14 +26,23 @@
|
|||
}
|
||||
|
||||
.element-options {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
gap: $s-8;
|
||||
width: 100%;
|
||||
height: calc(100vh - $s-80);
|
||||
padding-top: $s-8;
|
||||
}
|
||||
|
||||
.design-options,
|
||||
.interaction-options {
|
||||
overflow: auto;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.inspect {
|
||||
scrollbar-gutter: unset;
|
||||
}
|
||||
|
||||
.options-tab-switcher {
|
||||
--tabs-nav-padding-inline-end: 12px;
|
||||
}
|
||||
|
|
|
@ -8,44 +8,7 @@
|
|||
|
||||
.stroke-data {
|
||||
@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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
.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
|
||||
"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
|
||||
"A conj like function for js arrays."
|
||||
|
@ -49,3 +51,19 @@
|
|||
"A specific filter for js arrays."
|
||||
[pred ^js/Array o]
|
||||
(.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
Reference in a new issue