From 21217c5622447edfbe6d8b0821da11eec6be33d4 Mon Sep 17 00:00:00 2001 From: Dream <42954461+eureka0928@users.noreply.github.com> Date: Thu, 9 Apr 2026 03:32:56 -0400 Subject: [PATCH] :sparkles: Add per-group add button for typographies (#8895) * :sparkles: Add per-group add button for typographies Add a "+" button to each typography group header, allowing users to create new typographies directly inside a group instead of only at the top level. The button only appears for local, editable files. Closes #5275 * :books: Add changelog entry for typography group add button * :bug: Fix typography group title button layout wrapping * :recycle: Address review feedback for typography group add button Signed-off-by: eureka928 --- CHANGES.md | 1 + .../src/app/main/data/workspace/texts.cljs | 79 ++++++++++--------- .../ui/workspace/sidebar/assets/groups.cljs | 7 +- .../ui/workspace/sidebar/assets/groups.scss | 1 + .../sidebar/assets/typographies.cljs | 21 +++-- 5 files changed, 66 insertions(+), 43 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac952bd18c..09afc6beb2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ - Add drag-to-change for numeric inputs in workspace sidebar [Github #2466](https://github.com/penpot/penpot/issues/2466) - Add CSS linter [Taiga #13790](https://tree.taiga.io/project/penpot/us/13790) - Save and restore selection state in undo/redo (by @eureka928) [Github #6007](https://github.com/penpot/penpot/issues/6007) +- Add per-group add button for typographies (by @eureka928) [Github #5275](https://github.com/penpot/penpot/issues/5275) ### :bug: Bugs fixed diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 46ae233a2b..68c389d467 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -859,50 +859,55 @@ "A higher level version of dwl/add-typography, and has mainly two responsabilities: add the typography to the library and apply it to the currently selected text shapes (being aware of the open text - editors." - [file-id] - (ptk/reify ::add-typography - ptk/WatchEvent - (watch [_ state _] - (let [selected (dsh/lookup-selected state) - objects (dsh/lookup-page-objects state) + editors. + Optionally accepts a group-path to place the new typography inside + a specific group." + ([file-id] (add-typography file-id nil)) + ([file-id group-path] + (ptk/reify ::add-typography + ptk/WatchEvent + (watch [_ state _] + (let [selected (dsh/lookup-selected state) + objects (dsh/lookup-page-objects state) - xform (comp (keep (d/getf objects)) - (filter cfh/text-shape?)) - shapes (into [] xform selected) - shape (first shapes) + xform (comp (keep (d/getf objects)) + (filter cfh/text-shape?)) + shapes (into [] xform selected) + shape (first shapes) - values (current-text-values - {:editor-state (dm/get-in state [:workspace-editor-state (:id shape)]) - :shape shape - :attrs txt/text-node-attrs}) + values (current-text-values + {:editor-state (dm/get-in state [:workspace-editor-state (:id shape)]) + :shape shape + :attrs txt/text-node-attrs}) - multiple? (or (> 1 (count shapes)) - (d/seek (partial = :multiple) - (vals values))) + multiple? (or (> 1 (count shapes)) + (d/seek (partial = :multiple) + (vals values))) - values (-> (d/without-nils values) - (select-keys - (d/concat-vec txt/text-font-attrs - txt/text-spacing-attrs - txt/text-transform-attrs))) + values (-> (d/without-nils values) + (select-keys + (d/concat-vec txt/text-font-attrs + txt/text-spacing-attrs + txt/text-transform-attrs))) - typ-id (uuid/next) - typ (-> (if multiple? - txt/default-typography - (merge txt/default-typography values)) - (generate-typography-name) - (assoc :id typ-id))] + typ-id (uuid/next) + typ (-> (if multiple? + txt/default-typography + (merge txt/default-typography values)) + (generate-typography-name) + (assoc :id typ-id) + (cond-> (string? group-path) + (update :name #(str group-path " / " %))))] - (rx/concat - (rx/of (dwl/add-typography typ) - (ptk/event ::ev/event {::ev/name "add-asset-to-library" - :asset-type "typography"})) + (rx/concat + (rx/of (dwl/add-typography typ) + (ptk/event ::ev/event {::ev/name "add-asset-to-library" + :asset-type "typography"})) - (when (not multiple?) - (rx/of (update-attrs (:id shape) - {:typography-ref-id typ-id - :typography-ref-file file-id})))))))) + (when (not multiple?) + (rx/of (update-attrs (:id shape) + {:typography-ref-id typ-id + :typography-ref-file file-id}))))))))) ;; -- New Editor diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs index fa81b21157..3bc5fe58f1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.cljs @@ -23,7 +23,7 @@ [rumext.v2 :as mf])) (mf/defc asset-group-title* - [{:keys [file-id section path is-group-open on-rename on-ungroup on-group-combine-variants is-can-combine]}] + [{:keys [file-id section path is-group-open on-rename on-ungroup on-group-combine-variants is-can-combine on-add]}] (when-not (empty? path) (let [[other-path last-path truncated] (cpn/compact-path path 35 true) menu-state (mf/use-state cmm/initial-context-menu-state) @@ -76,6 +76,11 @@ :handler #(on-group-combine-variants path)}))}]] [:div {:class (stl/css :title-menu)} + (when on-add + [:> icon-button* {:variant "ghost" + :aria-label (tr "workspace.assets.typography.add-typography") + :on-click on-add + :icon i/add}]) [:> icon-button* {:variant "ghost" :aria-label (tr "workspace.assets.component-group-options") :on-click on-context-menu diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss index a2db8f408b..12a50ea556 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/groups.scss @@ -25,6 +25,7 @@ } .title-menu { + display: flex; visibility: hidden; } diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs index d222ffafd0..d9665d4bba 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/typographies.cljs @@ -133,7 +133,7 @@ {::mf/wrap-props false} [{:keys [file-id prefix groups open-groups force-open? file local? selected local-data editing-id renaming-id on-asset-click handle-change on-rename-group - on-ungroup on-context-menu selected-full]}] + on-ungroup on-context-menu selected-full is-read-only]}] (let [group-open? (if (false? (get open-groups prefix)) ;; if the user has closed it specifically, respect that false (get open-groups prefix true)) @@ -164,7 +164,14 @@ (mf/use-fn (mf/deps dragging* prefix selected-paths selected-full move-typography) (fn [event] - (cmm/on-drop-asset-group event dragging* prefix selected-paths selected-full move-typography)))] + (cmm/on-drop-asset-group event dragging* prefix selected-paths selected-full move-typography))) + + add-typography-to-group + (mf/use-fn + (mf/deps file-id prefix) + (fn [_] + (st/emit! (dw/set-assets-section-open file-id :typographies true) + (dwt/add-typography file-id prefix))))] [:div {:class (stl/css :typographies-group) :on-drag-enter on-drag-enter @@ -176,7 +183,9 @@ :path prefix :is-group-open group-open? :on-rename on-rename-group - :on-ungroup on-ungroup}] + :on-ungroup on-ungroup + :on-add (when (and local? (not is-read-only)) + add-typography-to-group)}] (when group-open? [:* @@ -229,7 +238,8 @@ :on-rename-group on-rename-group :on-ungroup on-ungroup :on-context-menu on-context-menu - :selected-full selected-full}]))])])) + :selected-full selected-full + :is-read-only is-read-only}]))])])) (mf/defc typographies-section* [{:keys [file file-id typographies open-status-ref selected @@ -431,7 +441,8 @@ :on-rename-group on-rename-group :on-ungroup on-ungroup :on-context-menu on-context-menu - :selected-full selected-full}] + :selected-full selected-full + :is-read-only read-only?}] (if is-local [:> cmm/assets-context-menu*