Compare commits

...

4 Commits

Author SHA1 Message Date
Juan de la Cruz
7066afa01a 🎉 Add new slides 2.14 content (#8478) 2026-02-26 12:19:15 +01:00
Eva Marco
9345902a62 🐛 Fix cannot apply second token after creation while shape is selected (#8476) 2026-02-26 10:53:25 +01:00
Alonso Torres
a4190df073 🐛 Fix problem with flex.appendChild with naturalOrdering on plugins API (#8470) 2026-02-26 10:47:44 +01:00
Eva Marco
47dae090ed 🐛 Add notification to token applied during text edition (#8434) 2026-02-26 10:24:48 +01:00
15 changed files with 320 additions and 16 deletions

View File

@@ -31,6 +31,7 @@
- Fix boolean operators in menu for boards [Taiga #13174](https://tree.taiga.io/project/penpot/issue/13174)
- Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186)
- Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128)
- Fix cannot apply second token after creation while shape is selected [Taiga #13513](https://tree.taiga.io/project/penpot/issue/13513)
## 2.13.3

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

View File

@@ -620,7 +620,7 @@
ptk/WatchEvent
(watch [_ state _]
;; We do not allow to apply tokens while text editor is open.
(when (empty? (get state :workspace-editor-state))
(if (empty? (get state :workspace-editor-state))
(let [attributes-to-remove
;; Remove atomic typography tokens when applying composite and vice-verca
(cond
@@ -674,7 +674,11 @@
(if (rx/observable? res)
res
(rx/of res))))
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))))
(rx/of (dwu/commit-undo-transaction undo-id)))))))))
(rx/of (ntf/show {:content (tr "workspace.tokens.error-text-edition")
:type :toast
:level :warning
:timeout 3000}))))))
(defn apply-spacing-token-separated
"Handles edge-case for spacing token when applying token via toggle button.

View File

@@ -32,6 +32,7 @@
[app.main.ui.releases.v2-11]
[app.main.ui.releases.v2-12]
[app.main.ui.releases.v2-13]
[app.main.ui.releases.v2-14]
[app.main.ui.releases.v2-2]
[app.main.ui.releases.v2-3]
[app.main.ui.releases.v2-4]
@@ -104,4 +105,4 @@
(defmethod rc/render-release-notes "0.0"
[params]
(rc/render-release-notes (assoc params :version "2.13")))
(rc/render-release-notes (assoc params :version "2.14")))

View File

@@ -0,0 +1,178 @@
;; 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.releases.v2-14
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.ui.releases.common :as c]
[rumext.v2 :as mf]))
(defmethod c/render-release-notes "2.14"
[{:keys [slide klass next finish navigate version]}]
(mf/html
(case slide
:start
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.14-slide-0.jpg"
:class (stl/css :start-image)
:border "0"
:alt "Penpot 2.14 is here!"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Whats new in Penpot?"]
[:div {:class (stl/css :version-tag)}
(dm/str "Version " version)]]
[:div {:class (stl/css :features-block)}
[:span {:class (stl/css :feature-title)}
"Design tokens, but friendlier (and a bit faster, too)"]
[:p {:class (stl/css :feature-content)}
"This release keeps pushing Penpots design system foundations forward, with a big focus on design tokens. Were making long token names easier to navigate, opening up tokens in the plugins API, and tackling one of the trickiest moments in token workflows: renaming (without breaking everything)."]
[:p {:class (stl/css :feature-content)}
"On top of that, youll find a handful of quality-of-life improvements and some performance work in the sidebar to keep things feeling smooth as your files grow. Lets dive in."]
[:p {:class (stl/css :feature-content)}
"Lets dive in!"]]
[:div {:class (stl/css :navigation)}
[:button {:class (stl/css :next-btn)
:on-click next} "Continue"]]]]]]
0
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.14-tokens-fold.gif"
:class (stl/css :start-image)
:border "0"
:alt "Token groups: Navigating long names, finally"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Token groups: Navigating long names, finally"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Token names are rarely short and sweet. Most of the time they carry a lot of meaning (type, state, property, variant… and more), which is great for consistency, but not so great for browsing. In 2.14 were introducing token groups, a new way to navigate dotted token paths as nested, collapsible sections."]
[:p {:class (stl/css :feature-content)}
"Token segments before the final name are displayed as groups, and only the last segment stays as a pill (so you keep the familiar token “chip” where it matters). If you unfold a path, it stays open while you move around the app (it resets only when the page reloads). And when you create a new token, Penpot automatically unfolds the path needed to reveal it (even if it overrides a previously opened one)."]
[:p {:class (stl/css :feature-content)}
"One extra detail: if you edit the path and change group segments, the token is moved to its new group (creating it if needed), and empty groups are automatically cleaned up."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 4}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
1
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.14-api.gif"
:class (stl/css :start-image)
:border "0"
:alt "Design tokens in the plugins API: Automation unlocked"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Design tokens in the plugins API: Automation unlocked"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Design tokens are now available in the Penpot plugins API. That means plugins (and external tools built around Penpot, like AI clients or Penpot MCP) can finally work with tokens programmatically and automate token workflows that used to be purely manual."]
[:p {:class (stl/css :feature-content)}
"If youve been waiting to generate tokens, sync them, or manipulate them from your own tools, this is the missing piece. And yes, this one has been requested a lot."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 4}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
2
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.14-remap.jpg"
:class (stl/css :start-image)
:border "0"
:alt "Rename tokens without breaking everything"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Rename tokens without breaking everything"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Renaming tokens sounds simple until you remember the references. One change can ripple through aliases, applied tokens, tooltips, math operations… and suddenly youre left with a broken chain. In 2.14, renaming a token can optionally remap its references, keeping connections intact and updating the design with the new token name."]
[:p {:class (stl/css :feature-content)}
"Remapping is always optional, because sometimes you dont want to keep the current connections. When enabled, it affects all tokens in the file and also takes libraries into account, so main components can propagate changes to child components, and applied tokens update on the elements using them."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 4}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
3
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.14-icons.gif"
:class (stl/css :start-image)
:border "0"
:alt "Quality-of-life improvements"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Quality-of-life improvements"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Lock and hide controls in the layer panel are getting a usability boost. The lock and visibility icons stay fixed in a right-aligned column regardless of indentation, and scrolling wont make them awkward to click (even in deeply nested files)."]
[:p {:class (stl/css :feature-content)}
"Were also improving sidebar performance, with a focus on keeping interactions fluent. The goal is to lazy-load the shape list on-demand and avoid UI stalls when clicking or hovering around the sidebar."]
[:p {:class (stl/css :feature-content)}
"And one more: you can now use Shift/Alt arrow key stepping in color picker inputs (a community contribution by @eureka928. ❤️)"]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 4}]
[:button {:on-click finish
:class (stl/css :next-btn)} "Let's go"]]]]]])))

View File

@@ -0,0 +1,102 @@
// 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
@use "refactor/common-refactor.scss" as deprecated;
.modal-overlay {
@extend .modal-overlay-base;
}
.modal-container {
display: grid;
grid-template-columns: deprecated.$s-324 1fr;
height: deprecated.$s-500;
width: deprecated.$s-888;
border-radius: deprecated.$br-8;
background-color: var(--modal-background-color);
border: deprecated.$s-2 solid var(--modal-border-color);
}
.start-image {
width: deprecated.$s-324;
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
}
.modal-content {
padding: deprecated.$s-40;
display: grid;
grid-template-rows: auto 1fr deprecated.$s-32;
gap: deprecated.$s-24;
a {
color: var(--button-primary-background-color-rest);
}
}
.modal-header {
display: grid;
gap: deprecated.$s-8;
}
.version-tag {
@include deprecated.flexCenter;
@include deprecated.headlineSmallTypography;
height: deprecated.$s-32;
width: deprecated.$s-96;
background-color: var(--communication-tag-background-color);
color: var(--communication-tag-foreground-color);
border-radius: deprecated.$br-8;
}
.modal-title {
@include deprecated.headlineLargeTypography;
color: var(--modal-title-foreground-color);
}
.features-block {
display: flex;
flex-direction: column;
gap: deprecated.$s-16;
width: deprecated.$s-440;
}
.feature {
display: flex;
flex-direction: column;
gap: deprecated.$s-8;
}
.feature-title {
@include deprecated.bodyLargeTypography;
color: var(--modal-title-foreground-color);
}
.feature-content {
@include deprecated.bodyMediumTypography;
margin: 0;
color: var(--modal-text-foreground-color);
}
.feature-list {
@include deprecated.bodyMediumTypography;
color: var(--modal-text-foreground-color);
list-style: disc;
display: grid;
gap: deprecated.$s-8;
}
.navigation {
width: 100%;
display: grid;
grid-template-areas: "bullets button";
}
.next-btn {
@extend .button-primary;
width: deprecated.$s-100;
justify-self: flex-end;
grid-area: button;
}

View File

@@ -12,6 +12,7 @@
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs]
@@ -132,13 +133,17 @@
on-token-pill-click
(mf/use-fn
(mf/deps not-editing? selected-ids)
(mf/deps not-editing? selected-ids tokens-lib)
(fn [event token]
(let [token (ctob/get-token tokens-lib selected-token-set-id (:id token))]
(dom/stop-propagation event)
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
(if (and not-editing? (seq selected-shapes) (not= (:type token) :number))
(st/emit! (dwta/toggle-token {:token token
:shape-ids selected-ids}))))))]
:shape-ids selected-ids}))
(st/emit! (ntf/show {:content (tr "workspace.tokens.error-text-edition")
:type :toast
:level :warning
:timeout 3000}))))))]
[:div {:class (stl/css :token-section-wrapper)
:data-testid (dm/str "section-" (name type))}

View File

@@ -10,12 +10,12 @@
[app.common.schema :as sm]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.shapes :as dwsh]
[app.main.store :as st]
[app.plugins.flags :refer [natural-child-ordering?]]
[app.plugins.register :as r]
[app.plugins.utils :as u]
[app.util.object :as obj]
[potok.v2.core :as ptk]))
[app.util.object :as obj]))
;; Define in `app.plugins.shape` we do this way to prevent circular dependency
(def shape-proxy? nil)
@@ -259,9 +259,13 @@
(u/display-not-valid :appendChild child)
:else
(let [child-id (obj/get child "$id")]
(st/emit! (dwt/move-shapes-to-frame #{child-id} id nil nil)
(ptk/data-event :layout/update {:ids [id]})))))))
(let [child-id (obj/get child "$id")
shape (u/locate-shape file-id page-id id)
index
(if (and (natural-child-ordering? plugin-id) (not (ctl/reverse? shape)))
0
(count (:shapes shape)))]
(st/emit! (dwsh/relocate-shapes #{child-id} id index)))))))
(defn layout-child-proxy? [p]
(obj/type-of? p "LayoutChildProxy"))

View File

@@ -962,9 +962,10 @@
:else
(let [child-id (obj/get child "$id")
is-reversed? (ctl/flex-layout? shape)
index (if (and (natural-child-ordering? plugin-id) is-reversed?)
0
(count (:shapes shape)))]
index
(if (or (not (natural-child-ordering? plugin-id)) is-reversed?)
0
(count (:shapes shape)))]
(st/emit! (dwsh/relocate-shapes #{child-id} id index))))))
:insertChild
@@ -987,7 +988,7 @@
(let [child-id (obj/get child "$id")
is-reversed? (ctl/flex-layout? shape)
index
(if (and (natural-child-ordering? plugin-id) is-reversed?)
(if (or (not (natural-child-ordering? plugin-id)) is-reversed?)
(- (count (:shapes shape)) index)
index)]
(st/emit! (dwsh/relocate-shapes #{child-id} id index))))))

View File

@@ -8366,6 +8366,10 @@ msgstr "Invalid value: Units are not allowed."
msgid "workspace.tokens.warning-name-change"
msgstr "Renaming this token will break any reference to its old name"
#: src/app/main/data/workspace/tokens/application.cljs
msgid "workspace.tokens.error-text-edition"
msgstr "Tokens can't be applied while editing text. Select the text layer instead."
#: src/app/main/ui/workspace/sidebar.cljs:159, src/app/main/ui/workspace/sidebar.cljs:166
msgid "workspace.toolbar.assets"
msgstr "Assets"

View File

@@ -8246,6 +8246,10 @@ msgstr ""
"Cambiar el nombre de este token romperá cualquier referencia a su nombre "
"anterior."
#: src/app/main/data/workspace/tokens/application.cljs
msgid "workspace.tokens.error-text-edition"
msgstr "No se pueden aplicar tokens mientras se edita texto. Seleccione la capa de texto en su lugar."
#: src/app/main/ui/workspace/sidebar.cljs:159, src/app/main/ui/workspace/sidebar.cljs:166
msgid "workspace.toolbar.assets"
msgstr "Recursos"