mirror of https://github.com/penpot/penpot.git
✨ Add shortcuts for creating variants and properties (#7181)
* ✨ Add shortcuts for creating variants and properties * 📎 PR changes
This commit is contained in:
parent
d80ef17623
commit
f1e7149e88
|
|
@ -50,9 +50,8 @@
|
|||
(def ref:annotations-state
|
||||
(l/derived :workspace-annotations st/state))
|
||||
|
||||
(mf/defc component-annotation
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
(mf/defc component-annotation*
|
||||
{::mf/private true}
|
||||
[{:keys [id shape component rerender-fn]}]
|
||||
(let [main-instance? (:main-instance shape)
|
||||
component-id (:component-id shape)
|
||||
|
|
@ -500,8 +499,7 @@
|
|||
(tr "workspace.options.component.variant.duplicated.copy.locate")]]))]))
|
||||
|
||||
|
||||
(mf/defc component-swap-item
|
||||
{::mf/props :obj}
|
||||
(mf/defc component-swap-item*
|
||||
[{:keys [item loop shapes file-id root-shape container component-id is-search listing-thumbs num-variants]}]
|
||||
(let [on-select
|
||||
(mf/use-fn
|
||||
|
|
@ -535,8 +533,7 @@
|
|||
[:span {:class (stl/css-case :variant-mark-cell listing-thumbs :variant-icon true)
|
||||
:title (tr "workspace.assets.components.num-variants" num-variants)} i/variant])]))
|
||||
|
||||
(mf/defc component-group-item
|
||||
{::mf/props :obj}
|
||||
(mf/defc component-group-item*
|
||||
[{:keys [item on-enter-group]}]
|
||||
(let [group-name (:name item)
|
||||
on-group-click #(on-enter-group group-name)]
|
||||
|
|
@ -571,8 +568,7 @@
|
|||
(= (:component-id shape-a)
|
||||
(:component-id shape-b)))
|
||||
|
||||
(mf/defc component-swap
|
||||
{::mf/props :obj}
|
||||
(mf/defc component-swap*
|
||||
[{:keys [shapes]}]
|
||||
(let [single? (= 1 (count shapes))
|
||||
shape (first shapes)
|
||||
|
|
@ -753,7 +749,7 @@
|
|||
(when (:listing-thumbs? filters)
|
||||
[:div {:class (stl/css :component-list)}
|
||||
(for [item groups]
|
||||
[:& component-group-item {:item item :on-enter-group on-enter-group}])])
|
||||
[:> component-group-item* {:item item :on-enter-group on-enter-group}])])
|
||||
|
||||
[:div {:class (stl/css-case :component-grid (:listing-thumbs? filters)
|
||||
:component-list (not (:listing-thumbs? filters)))}
|
||||
|
|
@ -766,24 +762,23 @@
|
|||
(keep :component-id)
|
||||
set)
|
||||
loop? (some #(contains? components %) parent-components)]
|
||||
[:& component-swap-item {:key (dm/str (:id item))
|
||||
:item item
|
||||
:loop loop?
|
||||
:shapes shapes
|
||||
:file-id current-library-id
|
||||
:root-shape root-shape
|
||||
:container container
|
||||
:component-id component-id
|
||||
:is-search is-search?
|
||||
:listing-thumbs (:listing-thumbs? filters)
|
||||
:num-variants (count-variants item)}])
|
||||
[:> component-swap-item* {:key (dm/str (:id item))
|
||||
:item item
|
||||
:loop loop?
|
||||
:shapes shapes
|
||||
:file-id current-library-id
|
||||
:root-shape root-shape
|
||||
:container container
|
||||
:component-id component-id
|
||||
:is-search is-search?
|
||||
:listing-thumbs (:listing-thumbs? filters)
|
||||
:num-variants (count-variants item)}])
|
||||
|
||||
[:& component-group-item {:item item
|
||||
:key (:name item)
|
||||
:on-enter-group on-enter-group}]))]]]]))
|
||||
[:> component-group-item* {:item item
|
||||
:key (:name item)
|
||||
:on-enter-group on-enter-group}]))]]]]))
|
||||
|
||||
(mf/defc component-ctx-menu
|
||||
{::mf/props :obj}
|
||||
(mf/defc component-ctx-menu*
|
||||
[{:keys [menu-entries on-close show main-instance]}]
|
||||
(let [do-action
|
||||
(fn [action event]
|
||||
|
|
@ -879,8 +874,20 @@
|
|||
(when can-swap? (st/emit! (dwsp/open-specialized-panel :component-swap)))
|
||||
(tm/schedule-on-idle #(dom/focus! (dom/get-element search-id))))))
|
||||
|
||||
create-variant
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
#(st/emit! (dwv/add-new-variant id)))
|
||||
|
||||
add-new-property
|
||||
(mf/use-fn
|
||||
(mf/deps shape)
|
||||
#(st/emit! (dwv/add-new-property (:variant-id shape) {:property-value "Value 1"
|
||||
:editing? true})))
|
||||
|
||||
on-combine-as-variants
|
||||
#(st/emit! (dwv/combine-as-variants))
|
||||
(mf/use-fn
|
||||
#(st/emit! (dwv/combine-as-variants)))
|
||||
|
||||
;; NOTE: function needed for force rerender from the bottom
|
||||
;; components. This is because `component-annotation`
|
||||
|
|
@ -903,7 +910,8 @@
|
|||
(if swap-opened?
|
||||
[:button {:class (stl/css :title-back)
|
||||
:on-click on-component-back}
|
||||
[:span {:class (stl/css :icon-back)} i/arrow]
|
||||
[:> icon* {:icon-id "arrow-left"
|
||||
:size "s"}]
|
||||
[:span (tr "workspace.options.component")]]
|
||||
|
||||
[:*
|
||||
|
|
@ -921,60 +929,74 @@
|
|||
(tr "workspace.options.component.copy"))]]
|
||||
|
||||
(when is-variant?
|
||||
[:div {:class (stl/css :variants-help-modal-button)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.options.component.variants-help-modal.title")
|
||||
:on-click on-click-variant-title-help
|
||||
:icon "help"}]])])]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.options.component.variants-help-modal.title")
|
||||
:on-click on-click-variant-title-help
|
||||
:icon "help"}])
|
||||
|
||||
(when (and is-variant? main-instance?)
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant")
|
||||
:on-click create-variant
|
||||
:icon "variant"}])])]
|
||||
|
||||
(when open?
|
||||
[:div {:class (stl/css :element-content)}
|
||||
[:div {:class (stl/css-case :component-wrapper true
|
||||
:with-actions show-menu?
|
||||
:without-actions (not show-menu?))}
|
||||
[:button {:class (stl/css-case :component-name-wrapper true
|
||||
:with-main (and can-swap? (not multi))
|
||||
:swappeable (and can-swap? (not swap-opened?)))
|
||||
:data-testid "swap-component-btn"
|
||||
:on-click open-component-panel}
|
||||
[:div {:class (stl/css :component-line)}
|
||||
|
||||
[:span {:class (stl/css :component-icon)}
|
||||
(if main-instance?
|
||||
(if is-variant?
|
||||
i/variant
|
||||
i/component)
|
||||
i/component-copy)]
|
||||
[:div {:class (stl/css :component-wrapper)}
|
||||
|
||||
[:div {:class (stl/css :name-wrapper)}
|
||||
[:div {:class (stl/css :component-name)}
|
||||
[:span {:class (stl/css :component-name-inside)}
|
||||
(if (and multi (not same-variant?))
|
||||
(tr "settings.multiple")
|
||||
(cfh/last-path shape-name))]]
|
||||
[:button {:class (stl/css-case :component-name-wrapper true
|
||||
:without-menu (not show-menu?)
|
||||
:swappeable (and can-swap? (not swap-opened?)))
|
||||
:data-testid "swap-component-btn"
|
||||
:on-click open-component-panel}
|
||||
|
||||
(when (and can-swap? (not multi))
|
||||
[:div {:class (stl/css :component-parent-name)}
|
||||
(if (:deleted component)
|
||||
(tr "workspace.options.component.unlinked")
|
||||
(cfh/merge-path-item-with-dot path (:name component)))])]]
|
||||
[:div {:class (stl/css :component-icon)}
|
||||
[:> icon* {:size "s"
|
||||
:icon-id (if main-instance?
|
||||
(if is-variant? "variant" "component")
|
||||
"component-copy")}]]
|
||||
|
||||
(when show-menu?
|
||||
[:div {:class (stl/css :component-actions)}
|
||||
[:button {:class (stl/css-case :menu-btn true
|
||||
:selected menu-open?)
|
||||
:on-click on-menu-click}
|
||||
i/menu]
|
||||
[:div {:class (stl/css :component-name-outside)}
|
||||
[:div {:class (stl/css :component-name)}
|
||||
[:span {:class (stl/css :component-name-inside)}
|
||||
(if (and multi (not same-variant?))
|
||||
(tr "settings.multiple")
|
||||
(cfh/last-path shape-name))]]
|
||||
|
||||
[:& component-ctx-menu {:show menu-open?
|
||||
:on-close on-menu-close
|
||||
:menu-entries menu-entries
|
||||
:main-instance main-instance?}]])]
|
||||
(when (and can-swap? (not multi))
|
||||
[:div {:class (stl/css :component-parent-name)}
|
||||
(if (:deleted component)
|
||||
(tr "workspace.options.component.unlinked")
|
||||
(cfh/merge-path-item-with-dot path (:name component)))])]]
|
||||
|
||||
(when show-menu?
|
||||
[:div {:class (stl/css :component-actions)}
|
||||
[:button {:class (stl/css-case :component-menu-btn true
|
||||
:selected menu-open?)
|
||||
:on-click on-menu-click}
|
||||
[:> icon* {:icon-id "menu"}]]
|
||||
|
||||
[:> component-ctx-menu* {:show menu-open?
|
||||
:on-close on-menu-close
|
||||
:menu-entries menu-entries
|
||||
:main-instance main-instance?}]])]
|
||||
|
||||
(when (and is-variant? main-instance?)
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant-property")
|
||||
:on-click add-new-property
|
||||
:icon "add"}])]
|
||||
|
||||
(when swap-opened?
|
||||
[:& component-swap {:shapes copies}])
|
||||
[:> component-swap* {:shapes copies}])
|
||||
|
||||
(when (and (not swap-opened?) (not multi))
|
||||
[:& component-annotation {:id id :shape shape :component component :rerender-fn rerender-fn}])
|
||||
[:> component-annotation* {:id id
|
||||
:shape shape
|
||||
:component component
|
||||
:rerender-fn rerender-fn}])
|
||||
|
||||
(when (and is-variant?
|
||||
(not main-instance?)
|
||||
|
|
@ -1044,11 +1066,21 @@
|
|||
menu-open* (mf/use-state false)
|
||||
menu-open? (deref menu-open*)
|
||||
|
||||
menu-entries [{:title (tr "workspace.shape.menu.add-variant-property")
|
||||
:action #(st/emit! (dwv/add-new-property variant-id {:property-value "Value 1"
|
||||
:editing? true}))}
|
||||
{:title (tr "workspace.shape.menu.add-variant")
|
||||
:action #(st/emit! (dwv/add-new-variant (:id shape)))}]
|
||||
create-variant
|
||||
(mf/use-fn
|
||||
(mf/deps shape)
|
||||
#(st/emit! (dwv/add-new-variant (:id shape))))
|
||||
|
||||
add-new-property
|
||||
(mf/use-fn
|
||||
(mf/deps variant-id)
|
||||
#(st/emit! (dwv/add-new-property variant-id {:property-value "Value 1"
|
||||
:editing? true})))
|
||||
|
||||
menu-entries [{:title (tr "workspace.shape.menu.add-variant-property")
|
||||
:action add-new-property}
|
||||
{:title (tr "workspace.shape.menu.add-variant")
|
||||
:action create-variant}]
|
||||
|
||||
toggle-content
|
||||
(mf/use-fn
|
||||
|
|
@ -1118,42 +1150,52 @@
|
|||
[:span {:class (stl/css :copy-text)}
|
||||
(tr "workspace.options.component.main")]]
|
||||
|
||||
[:div {:class (stl/css :variants-help-modal-button)}
|
||||
[:div {:class (stl/css :title-actions)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.options.component.variants-help-modal.title")
|
||||
:on-click on-click-variant-title-help
|
||||
:icon "help"}]]]]
|
||||
:icon "help"}]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant")
|
||||
:on-click create-variant
|
||||
:icon "variant"}]]]]
|
||||
|
||||
(when open?
|
||||
[:div {:class (stl/css :element-content)}
|
||||
[:div {:class (stl/css-case :component-wrapper true
|
||||
:with-actions (not multi?)
|
||||
:without-actions multi?)}
|
||||
[:button {:class (stl/css-case :component-name-wrapper true
|
||||
:with-main true
|
||||
:swappeable false)}
|
||||
[:div {:class (stl/css :component-line)}
|
||||
|
||||
[:span {:class (stl/css :component-icon)} i/component]
|
||||
[:div {:class (stl/css :component-wrapper)}
|
||||
|
||||
[:div {:class (stl/css :name-wrapper)}
|
||||
[:div {:class (stl/css :component-name)}
|
||||
[:span {:class (stl/css :component-name-inside)}
|
||||
(if multi?
|
||||
(tr "settings.multiple")
|
||||
(cfh/last-path shape-name))]]]]
|
||||
[:button {:class (stl/css :component-name-wrapper)}
|
||||
|
||||
[:div {:class (stl/css :component-icon)}
|
||||
[:> icon* {:size "s"
|
||||
:icon-id "component"}]]
|
||||
|
||||
(when-not multi?
|
||||
[:div {:class (stl/css :component-actions)}
|
||||
[:button {:class (stl/css-case :menu-btn true
|
||||
:selected menu-open?)
|
||||
:on-click on-menu-click}
|
||||
i/menu]
|
||||
[:div {:class (stl/css :component-name-outside)}
|
||||
[:div {:class (stl/css :component-name)}
|
||||
[:span {:class (stl/css :component-name-inside)}
|
||||
(if multi?
|
||||
(tr "settings.multiple")
|
||||
(cfh/last-path shape-name))]]]]
|
||||
|
||||
[:& component-ctx-menu {:show menu-open?
|
||||
:on-close on-menu-close
|
||||
:menu-entries menu-entries
|
||||
:main-instance true}]])]
|
||||
(when-not multi?
|
||||
[:div {:class (stl/css :component-actions)}
|
||||
|
||||
[:button {:class (stl/css-case :component-menu-btn true
|
||||
:selected menu-open?)
|
||||
:on-click on-menu-click}
|
||||
[:> icon* {:icon-id "menu"}]]
|
||||
|
||||
[:> component-ctx-menu* {:show menu-open?
|
||||
:on-close on-menu-close
|
||||
:menu-entries menu-entries
|
||||
:main-instance true}]])]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.shape.menu.add-variant-property")
|
||||
:on-click add-new-property
|
||||
:icon "add"}]]
|
||||
|
||||
(when-not multi?
|
||||
[:div {:class (stl/css :variant-property-list)}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
.element-set {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, var(--sp-xxxl));
|
||||
column-gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.element-content {
|
||||
|
|
@ -21,9 +24,9 @@
|
|||
}
|
||||
|
||||
.element-title {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, var(--sp-xxxl));
|
||||
grid-column: span 8;
|
||||
column-gap: var(--sp-xs);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.title-back {
|
||||
|
|
@ -44,50 +47,31 @@
|
|||
.title-spacing-component {
|
||||
justify-content: flex-start;
|
||||
gap: $s-8;
|
||||
grid-column: span 7;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title-bar-variant {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.variants-help-modal-button {
|
||||
margin-inline-start: auto;
|
||||
grid-column: span 1;
|
||||
.title-actions {
|
||||
display: flex;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.icon-back {
|
||||
@include flexCenter;
|
||||
width: $s-12;
|
||||
height: 100%;
|
||||
|
||||
svg {
|
||||
height: $s-12;
|
||||
width: $s-12;
|
||||
stroke: var(--icon-foreground);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.component-wrapper {
|
||||
.component-line {
|
||||
grid-column: span 8;
|
||||
width: 100%;
|
||||
min-height: $s-32;
|
||||
border-radius: $br-8;
|
||||
display: flex;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
&.with-actions {
|
||||
display: flex;
|
||||
gap: $s-1;
|
||||
}
|
||||
|
||||
&.without-actions {
|
||||
padding-right: 0.5rem;
|
||||
|
||||
.component-name-wrapper {
|
||||
width: 100%;
|
||||
border-radius: $br-8;
|
||||
}
|
||||
}
|
||||
.component-wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
gap: $s-1;
|
||||
}
|
||||
|
||||
.component-name-wrapper {
|
||||
|
|
@ -102,24 +86,27 @@
|
|||
background-color: var(--assets-item-background-color);
|
||||
color: var(--assets-item-name-foreground-color-hover);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--assets-item-background-color-hover);
|
||||
color: var(--assets-item-name-foreground-color-hover);
|
||||
&.without-menu {
|
||||
width: 100%;
|
||||
border-radius: $br-8;
|
||||
}
|
||||
|
||||
&.swappeable {
|
||||
&:hover {
|
||||
background-color: var(--assets-item-background-color-hover);
|
||||
color: var(--assets-item-name-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-icon {
|
||||
@include flexCenter;
|
||||
display: flex;
|
||||
height: $s-32;
|
||||
width: $s-16;
|
||||
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
align-items: center;
|
||||
color: var(--icon-foreground);
|
||||
}
|
||||
|
||||
.name-wrapper {
|
||||
.component-name-outside {
|
||||
@include flexColumn;
|
||||
min-height: $s-32;
|
||||
padding: $s-8 0 $s-8 $s-2;
|
||||
|
|
@ -156,32 +143,16 @@
|
|||
width: $s-32;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
@extend .button-tertiary;
|
||||
.component-menu-btn {
|
||||
@extend .button-secondary;
|
||||
cursor: unset;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 0 $br-8 $br-8 0;
|
||||
background-color: var(--assets-item-background-color);
|
||||
color: var(--assets-item-name-foreground-color-hover);
|
||||
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
min-height: $s-16;
|
||||
min-width: $s-16;
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--assets-item-background-color-hover);
|
||||
color: var(--assets-item-name-foreground-color-hover);
|
||||
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-btn.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
|
||||
.copy-text {
|
||||
|
|
@ -192,10 +163,6 @@
|
|||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.swappeable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-select-dropdown {
|
||||
@extend .dropdown-wrapper;
|
||||
right: 0;
|
||||
|
|
@ -211,56 +178,6 @@
|
|||
@extend .dropdown-element-base;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.input-text {
|
||||
@include removeInputStyle;
|
||||
height: $s-32;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: $s-4;
|
||||
border: 0;
|
||||
font-size: $fs-12;
|
||||
color: var(--input-foreground-color-active);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--input-foreground-color-disabled);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
border-color: var(--input-border-outline-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
@include buttonStyle;
|
||||
@include flexCenter;
|
||||
height: $s-16;
|
||||
width: $s-16;
|
||||
|
||||
.clear-icon {
|
||||
@include flexCenter;
|
||||
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
@include flexCenter;
|
||||
margin-left: $s-8;
|
||||
flex: 0 0 $s-16;
|
||||
}
|
||||
|
||||
.component-path {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
Loading…
Reference in New Issue