Compare commits

...

2 Commits

Author SHA1 Message Date
Andrey Antukh
ecaffa5375 📎 Fix minor issues and linter reports 2026-02-23 13:01:27 +01:00
Dalai Felinto
4f6be55f9a Import Tokens from Linked Library
Add the option to import tokens from a linked library.

I know there are plans to link the tokens in together with the library.
Once this happens this patch can be reverted. Until then it helps a lot
to use a design system that relies on themes.

Before that someones would need to:
* Download the design system / add to their team.
* Open the file, download the tokens.

For every new file:
* Link the Design System library.
* Import the tokens file.

With this patch all you need to get started is to download the design
system and add to your team. From their importing the links is done on
the same pop-up that is used to import the tokens.

---

Technical considerations:

I try adding this as a dialog that is called once the library is
imported. I ran into a few issues though:

* To find whether the library has tokens (and thus show the dialog) I
  would need to extend library summary to include tokens.
* I couldn't find a reliable way to import the tokens after importing
  the library without resorting to a timer :/

I'm sure both of those hurdles are doable, I just wasted enough time
trying it to the point I decided on a different approach.

Signed-off-by: Dalai Felinto <dalai@blender.org>
2026-02-23 10:59:33 +01:00
7 changed files with 235 additions and 10 deletions

View File

@@ -14,6 +14,7 @@
- Add woff2 support on user uploaded fonts (by @Nivl) [Github #8248](https://github.com/penpot/penpot/pull/8248)
- Option to download custom fonts (by @dfelinto) [Github #8320](https://github.com/penpot/penpot/issues/8320)
- Add copy as image to clipboard option to workspace context menu (by @dfelinto) [Github #8313](https://github.com/penpot/penpot/pull/8313)
- Import Tokens from linked library [Github #8391](https://github.com/penpot/penpot/pull/8391)
### :bug: Bugs fixed

View File

@@ -119,12 +119,13 @@
:strict-session-cookies
:telemetry
:terms-and-privacy-checkbox
;; Only for developtment.
:tiered-file-data-storage
:token-base-font-size
:token-color
:token-shadow
:token-tokenscript
:token-import-from-library
;; Only for developtment.
:transit-readable-response
:user-feedback
;; TODO: remove this flag.
@@ -180,7 +181,8 @@
:enable-token-color
:enable-token-shadow
:enable-inspect-styles
:enable-feature-fdata-objects-map])
:enable-feature-fdata-objects-map
:enable-token-import-from-library])
(defn parse
[& flags]

View File

@@ -13,8 +13,10 @@
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.library :as ctl]
[app.common.types.tokens-lib :as ctob]
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
@@ -36,6 +38,7 @@
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.tokens.import-from-library]
[app.util.color :as uc]
[app.util.dom :as dom]
[app.util.i18n :refer [c tr]]
@@ -180,6 +183,12 @@
[summary]
(boolean (:is-empty summary)))
(defn- has-tokens?
"Check if library has tokens to be imported"
[{:keys [data]}]
(when-let [tokens-lib (get data :tokens-lib)]
(not (ctob/empty-lib? tokens-lib))))
(mf/defc libraries-tab*
{::mf/props :obj
::mf/private true}
@@ -230,14 +239,18 @@
(keep library-names))))
(sort-by (comp str/lower :name))))
linked-libraries-ids (mf/with-memo [linked-libraries]
(into #{} (map :id) linked-libraries))
linked-libraries-ids
(mf/with-memo [linked-libraries]
(into #{} d/xf:map-id linked-libraries))
importing*
(mf/use-state nil)
importing* (mf/use-state nil)
sample-libraries [{:id "penpot-design-system", :name "Design system example"}
{:id "wireframing-kit", :name "Wireframe library"}
{:id "whiteboarding-kit", :name "Whiteboarding Kit"}]
sample-libraries
(mf/with-memo []
[{:id "penpot-design-system", :name "Design system example"}
{:id "wireframing-kit", :name "Wireframe library"}
{:id "whiteboarding-kit", :name "Whiteboarding Kit"}])
change-search-term
@@ -267,6 +280,17 @@
(st/emit! (dwl/unlink-file-from-library file-id library-id)
(dwl/sync-file file-id library-id)))))
import-tokens
(mf/use-fn
(mf/deps file-id)
(fn [event]
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
(uuid/parse))]
(st/emit! (modal/show
:tokens/import-from-library {:file-id file-id
:library-id library-id})))))
on-delete-accept
(mf/use-fn
(mf/deps file-id)
@@ -332,8 +356,12 @@
:on-click publish}])]
(for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)]
[:div {:class (stl/css :section-list-item)
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
has-tokens? (and (has-tokens? library)
(contains? cf/flags :token-import-from-library))]
[:div {:class (if has-tokens?
(stl/css :section-list-item-double-icon)
(stl/css :section-list-item))
:key (dm/str id)
:data-testid "library-item"}
[:div {:class (stl/css :item-content)}
@@ -348,6 +376,15 @@
[:span {:class (stl/css :connected-to-values)} (str/join ", " connected-to-names)]
[:span ")"]])])]]
(when ^boolean has-tokens?
[:> icon-button*
{:type "button"
:aria-label (tr "workspace.libraries.import-tokens-btn")
:icon i/import-export
:data-library-id (dm/str id)
:variant "secondary"
:on-click import-tokens}])
[:> icon-button* {:type "button"
:aria-label (tr "workspace.libraries.unlink-library-btn")
:icon i/detach

View File

@@ -116,6 +116,11 @@
border-radius: $br-8;
}
.section-list-item-double-icon {
@extend .section-list-item;
grid-template-columns: 1fr auto auto;
}
.item-content {
height: fit-content;
}

View File

@@ -0,0 +1,92 @@
;; 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.workspace.tokens.import-from-library
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.data.modal :as modal]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.foundations.typography :as t]
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.util.i18n :refer [tr]]
[okulary.core :as l]
[rumext.v2 :as mf]))
(mf/defc import-modal-library*
{::mf/register modal/components
::mf/register-as :tokens/import-from-library}
[all-props]
(let [{:keys [file-id library-id]}
(js->clj all-props :keywordize-keys true)
library-file-ref (mf/with-memo [library-id]
(l/derived (fn [state]
(dm/get-in state [:files library-id :data]))
st/state))
library-data (mf/deref library-file-ref)
show-libraries-dialog
(mf/use-fn
(mf/deps file-id)
(fn []
(modal/hide!)
(modal/show! :libraries-dialog {:file-id file-id})))
cancel
(mf/use-fn
(fn []
(show-libraries-dialog)))
import
(mf/use-fn
(mf/deps file-id library-id library-data)
(fn []
(let [tokens-lib (:tokens-lib library-data)]
(st/emit! (dwtl/import-tokens-lib tokens-lib)))
(show-libraries-dialog)))]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)}
[:> icon-button* {:class (stl/css :close-btn)
:on-click cancel
:aria-label (tr "labels.close")
:variant "ghost"
:icon i/close}]
[:div {:class (stl/css :modal-header)}
[:> heading* {:level 2
:id "modal-title"
:typography "headline-large"
:class (stl/css :modal-title)}
(tr "modals.import-library-tokens.title")]]
[:div {:class (stl/css :modal-content)}
[:> text* {:as "p" :typography t/body-medium} (tr "modals.import-library-tokens.description")]]
[:> context-notification* {:type :context
:appearance "neutral"
:level "default"
:is-html true}
(tr "workspace.tokens.import-warning")]
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
[:> button* {:on-click cancel
:type "button"
:variant "secondary"}
(tr "labels.cancel")]
[:> button* {:on-click import
:type "button"
:variant "primary"}
(tr "modals.import-library-tokens.import")]]]]]))

View File

@@ -0,0 +1,70 @@
// 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;
@use "ds/typography.scss" as t;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.close-btn {
position: absolute;
inset-block-start: var(--sp-s);
inset-inline-end: var(--sp-s);
}
.modal-overlay {
--modal-title-foreground-color: var(--color-foreground-primary);
--modal-text-foreground-color: var(--color-foreground-secondary);
@extend .modal-overlay-base;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
inset-inline-start: 0;
inset-block-start: 0;
block-size: 100%;
inline-size: 100%;
background-color: var(--overlay-color);
}
.modal-dialog {
@extend .modal-container-base;
inline-size: 100%;
max-inline-size: 32rem;
max-block-size: unset;
user-select: none;
position: relative;
}
.modal-header {
margin-block-end: var(--sp-xxl);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
@include t.use-typography("headline-medium");
color: var(--modal-title-foreground-color);
word-break: break-word;
}
.modal-content {
@include t.use-typography("body-large");
color: var(--modal-text-foreground-color);
}
.modal-footer {
margin-block-start: var(--sp-xxl);
gap: var(--sp-s);
}
.action-buttons {
@extend .modal-action-btns;
gap: var(--sp-s);
}

View File

@@ -1153,6 +1153,20 @@ msgstr "Type to search results"
msgid "dashboard.unpublish-shared"
msgstr "Unpublish Library"
#:src/app/main/ui/workspace/tokens/import_from_library.cljs
msgid "modals.import-library-tokens.title"
msgstr "Import tokens from library?"
#:src/app/main/ui/workspace/tokens/import_from_library.cljs
msgid "modals.import-library-tokens.description"
msgstr ""
"The library has tokens and themes which "
"are likely used by its components."
#:src/app/main/ui/workspace/tokens/import_from_library.cljs
msgid "modals.import-library-tokens.import"
msgstr "Import tokens"
#: src/app/main/ui/settings/options.cljs:74
msgid "dashboard.update-settings"
msgstr "Update settings"
@@ -6008,6 +6022,10 @@ msgid_plural "workspace.libraries.typography"
msgstr[0] "1 typography"
msgstr[1] "%s typographies"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.import-tokens-btn"
msgstr "Import tokens"
#: src/app/main/ui/workspace/libraries.cljs:343
msgid "workspace.libraries.unlink-library-btn"
msgstr "Disconnect library"