mirror of
https://github.com/penpot/penpot.git
synced 2026-09-30 15:56:17 +00:00
🎉 Add tokens to the canvas (#11923)
* ✨ Show color tokens toggle on canvas background picker * 🐛 Apply token click on canvas background with no shape selected * ✨ Apply and persist color tokens on canvas background * 🌐 Translate canvas background section label
This commit is contained in:
parent
c44484a1e5
commit
7e3f779d8c
@ -264,6 +264,7 @@
|
|||||||
;; All props are optional, background can be nil because is the
|
;; All props are optional, background can be nil because is the
|
||||||
;; way to remove already set background
|
;; way to remove already set background
|
||||||
[:background {:optional true} [:maybe ctc/schema:hex-color]]
|
[:background {:optional true} [:maybe ctc/schema:hex-color]]
|
||||||
|
[:background-token {:optional true} [:maybe cto/schema:token-name]]
|
||||||
[:name {:optional true} :string]
|
[:name {:optional true} :string]
|
||||||
;; Pixel grid display controls — nil removes the per-page override
|
;; Pixel grid display controls — nil removes the per-page override
|
||||||
;; and falls back to the default hardcoded grid color/opacity.
|
;; and falls back to the default hardcoded grid color/opacity.
|
||||||
@ -878,6 +879,7 @@
|
|||||||
(fn [page]
|
(fn [page]
|
||||||
(let [name (get params :name)
|
(let [name (get params :name)
|
||||||
bg (get params :background :not-found)
|
bg (get params :background :not-found)
|
||||||
|
bg-token (get params :background-token :not-found)
|
||||||
grid-color (get params :pixel-grid-color :not-found)
|
grid-color (get params :pixel-grid-color :not-found)
|
||||||
grid-op (get params :pixel-grid-opacity :not-found)]
|
grid-op (get params :pixel-grid-opacity :not-found)]
|
||||||
(cond-> page
|
(cond-> page
|
||||||
@ -890,6 +892,12 @@
|
|||||||
(nil? bg)
|
(nil? bg)
|
||||||
(dissoc :background)
|
(dissoc :background)
|
||||||
|
|
||||||
|
(string? bg-token)
|
||||||
|
(assoc :background-token bg-token)
|
||||||
|
|
||||||
|
(and (not= bg-token :not-found) (nil? bg-token))
|
||||||
|
(dissoc :background-token)
|
||||||
|
|
||||||
(string? grid-color)
|
(string? grid-color)
|
||||||
(assoc :pixel-grid-color grid-color)
|
(assoc :pixel-grid-color grid-color)
|
||||||
|
|
||||||
|
|||||||
@ -242,7 +242,7 @@
|
|||||||
(let [page (::page (meta changes))]
|
(let [page (::page (meta changes))]
|
||||||
(mod-page changes page options)))
|
(mod-page changes page options)))
|
||||||
|
|
||||||
([changes page {:keys [name background pixel-grid-color pixel-grid-opacity]}]
|
([changes page {:keys [name background pixel-grid-color pixel-grid-opacity] :as options}]
|
||||||
(let [change {:type :mod-page :id (:id page)}
|
(let [change {:type :mod-page :id (:id page)}
|
||||||
redo (cond-> change
|
redo (cond-> change
|
||||||
(some? name)
|
(some? name)
|
||||||
@ -251,6 +251,9 @@
|
|||||||
(some? background)
|
(some? background)
|
||||||
(assoc :background background)
|
(assoc :background background)
|
||||||
|
|
||||||
|
(contains? options :background-token)
|
||||||
|
(assoc :background-token (:background-token options))
|
||||||
|
|
||||||
(some? pixel-grid-color)
|
(some? pixel-grid-color)
|
||||||
(assoc :pixel-grid-color pixel-grid-color)
|
(assoc :pixel-grid-color pixel-grid-color)
|
||||||
|
|
||||||
@ -264,6 +267,9 @@
|
|||||||
(some? background)
|
(some? background)
|
||||||
(assoc :background (:background page))
|
(assoc :background (:background page))
|
||||||
|
|
||||||
|
(contains? options :background-token)
|
||||||
|
(assoc :background-token (:background-token page))
|
||||||
|
|
||||||
(some? pixel-grid-color)
|
(some? pixel-grid-color)
|
||||||
(assoc :pixel-grid-color (:pixel-grid-color page))
|
(assoc :pixel-grid-color (:pixel-grid-color page))
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
[app.common.types.grid :as ctg]
|
[app.common.types.grid :as ctg]
|
||||||
[app.common.types.plugins :as ctpg]
|
[app.common.types.plugins :as ctpg]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
|
[app.common.types.token :as cto]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
@ -60,6 +61,8 @@
|
|||||||
[:guides {:optional true} schema:guides]
|
[:guides {:optional true} schema:guides]
|
||||||
[:plugin-data {:optional true} ctpg/schema:plugin-data]
|
[:plugin-data {:optional true} ctpg/schema:plugin-data]
|
||||||
[:background {:optional true} ctc/schema:hex-color]
|
[:background {:optional true} ctc/schema:hex-color]
|
||||||
|
;; Name of the color token applied to the background, if any.
|
||||||
|
[:background-token {:optional true} [:maybe cto/schema:token-name]]
|
||||||
;; Per-page pixel grid color. Falls back to a hardcoded default when
|
;; Per-page pixel grid color. Falls back to a hardcoded default when
|
||||||
;; unset so existing files render identically to before.
|
;; unset so existing files render identically to before.
|
||||||
[:pixel-grid-color {:optional true} ctc/schema:hex-color]
|
[:pixel-grid-color {:optional true} ctc/schema:hex-color]
|
||||||
|
|||||||
@ -605,6 +605,7 @@
|
|||||||
:dimensions [:dimensions]
|
:dimensions [:dimensions]
|
||||||
:fill [:color]
|
:fill [:color]
|
||||||
:stroke-color [:color]
|
:stroke-color [:color]
|
||||||
|
:canvas [:color]
|
||||||
:typography [:typography]
|
:typography [:typography]
|
||||||
:number [:number]
|
:number [:number]
|
||||||
:sizing [:sizing :dimensions]
|
:sizing [:sizing :dimensions]
|
||||||
|
|||||||
@ -331,6 +331,32 @@
|
|||||||
res (ch/process-changes data [chg])]
|
res (ch/process-changes data [chg])]
|
||||||
(t/is (= res data))))))
|
(t/is (= res data))))))
|
||||||
|
|
||||||
|
(t/deftest mod-page
|
||||||
|
(let [file-id (uuid/custom 2 2)
|
||||||
|
page-id (uuid/custom 1 1)
|
||||||
|
data (make-file-data file-id page-id)]
|
||||||
|
|
||||||
|
(t/testing "sets background and background-token together"
|
||||||
|
(let [chg {:type :mod-page
|
||||||
|
:id page-id
|
||||||
|
:background "#ffffff"
|
||||||
|
:background-token "brand.bg"}
|
||||||
|
res (ch/process-changes data [chg])
|
||||||
|
page (get-in res [:pages-index page-id])]
|
||||||
|
(t/is (= "#ffffff" (:background page)))
|
||||||
|
(t/is (= "brand.bg" (:background-token page)))))
|
||||||
|
|
||||||
|
(t/testing "clears background-token while leaving background untouched"
|
||||||
|
(let [data' (-> data
|
||||||
|
(assoc-in [:pages-index page-id :background] "#ffffff")
|
||||||
|
(assoc-in [:pages-index page-id :background-token] "brand.bg"))
|
||||||
|
chg {:type :mod-page
|
||||||
|
:id page-id
|
||||||
|
:background-token nil}
|
||||||
|
res (ch/process-changes data' [chg])
|
||||||
|
page (get-in res [:pages-index page-id])]
|
||||||
|
(t/is (= "#ffffff" (:background page)))
|
||||||
|
(t/is (not (contains? page :background-token)))))))
|
||||||
|
|
||||||
(t/deftest del-obj
|
(t/deftest del-obj
|
||||||
(let [file-id (uuid/custom 2 2)
|
(let [file-id (uuid/custom 2 2)
|
||||||
|
|||||||
@ -40,6 +40,32 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("User applies color token to the canvas background", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await setupTokensFileRender(page);
|
||||||
|
|
||||||
|
// No shape is selected, so the right sidebar shows the page/canvas
|
||||||
|
// options by default.
|
||||||
|
const canvasSection = page.getByRole("region", {
|
||||||
|
name: "Canvas background section",
|
||||||
|
});
|
||||||
|
await expect(canvasSection).toBeVisible();
|
||||||
|
|
||||||
|
// Open the color picker popover from the canvas background swatch.
|
||||||
|
await canvasSection.getByRole("button").first().click();
|
||||||
|
|
||||||
|
const colorPicker = page.getByTestId("colorpicker");
|
||||||
|
await expect(colorPicker).toBeVisible();
|
||||||
|
|
||||||
|
await colorPicker.getByTestId('opt-token-color').click();
|
||||||
|
await colorPicker.getByRole("button", { name: "black" }).click();
|
||||||
|
|
||||||
|
// The applied token badge replaces the plain color swatch/input in
|
||||||
|
// the canvas background row.
|
||||||
|
await expect(canvasSection.getByText("black")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test("User applies border-radius token to a shape from sidebar", async ({
|
test("User applies border-radius token to a shape from sidebar", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@ -1372,7 +1372,41 @@
|
|||||||
page (dsh/lookup-page state page-id)
|
page (dsh/lookup-page state page-id)
|
||||||
changes (-> (pcb/empty-changes it)
|
changes (-> (pcb/empty-changes it)
|
||||||
(pcb/with-page page)
|
(pcb/with-page page)
|
||||||
(pcb/mod-page {:background (:color color)}))]
|
(pcb/mod-page {:background (:color color)
|
||||||
|
:background-token nil}))]
|
||||||
|
(rx/of (dch/commit-changes changes)))))))
|
||||||
|
|
||||||
|
(defn apply-canvas-color-token
|
||||||
|
"Sets the canvas background to the resolved value of `token` and
|
||||||
|
remembers it as the applied token, so the background badge shows it."
|
||||||
|
([color token-name]
|
||||||
|
(apply-canvas-color-token nil color token-name))
|
||||||
|
([page-id color token-name]
|
||||||
|
(ptk/reify ::apply-canvas-color-token
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [page-id (or page-id (:current-page-id state))
|
||||||
|
page (dsh/lookup-page state page-id)
|
||||||
|
changes (-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-page page)
|
||||||
|
(pcb/mod-page {:background color
|
||||||
|
:background-token token-name}))]
|
||||||
|
(rx/of (dch/commit-changes changes)))))))
|
||||||
|
|
||||||
|
(defn detach-canvas-color-token
|
||||||
|
"Removes the applied-token link from the canvas background, keeping
|
||||||
|
the current background color as a plain value."
|
||||||
|
([]
|
||||||
|
(detach-canvas-color-token nil))
|
||||||
|
([page-id]
|
||||||
|
(ptk/reify ::detach-canvas-color-token
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [it state _]
|
||||||
|
(let [page-id (or page-id (:current-page-id state))
|
||||||
|
page (dsh/lookup-page state page-id)
|
||||||
|
changes (-> (pcb/empty-changes it)
|
||||||
|
(pcb/with-page page)
|
||||||
|
(pcb/mod-page {:background-token nil}))]
|
||||||
(rx/of (dch/commit-changes changes)))))))
|
(rx/of (dch/commit-changes changes)))))))
|
||||||
|
|
||||||
(defn change-pixel-grid-color
|
(defn change-pixel-grid-color
|
||||||
|
|||||||
@ -376,7 +376,8 @@
|
|||||||
:icon i/hsva
|
:icon i/hsva
|
||||||
:id "hsva"}])
|
:id "hsva"}])
|
||||||
|
|
||||||
show-tokens? (contains? #{:fill :stroke-color :color-selection} color-origin)]
|
show-tokens? (or (contains? #{:fill :stroke-color :color-selection} color-origin)
|
||||||
|
(and (= color-origin :canvas) (seq combined-tokens)))]
|
||||||
|
|
||||||
;; Initialize colorpicker state
|
;; Initialize colorpicker state
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
|
|||||||
@ -139,10 +139,10 @@
|
|||||||
|
|
||||||
on-token-pill-click
|
on-token-pill-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps selected-shapes)
|
(mf/deps selected-shapes color-origin)
|
||||||
(fn [event token]
|
(fn [event token]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(when (seq selected-shapes)
|
(when (or (= color-origin :canvas) (seq selected-shapes))
|
||||||
(on-token-change event token))))
|
(on-token-change event token))))
|
||||||
|
|
||||||
create-token-on-set
|
create-token-on-set
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.types.color :as clr]
|
[app.common.types.color :as clr]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
[app.main.data.workspace.tokens.application :as dwta]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
@ -24,6 +25,10 @@
|
|||||||
(-> (l/key :background)
|
(-> (l/key :background)
|
||||||
(l/derived refs/workspace-page)))
|
(l/derived refs/workspace-page)))
|
||||||
|
|
||||||
|
(def ^:private ref:background-token
|
||||||
|
(-> (l/key :background-token)
|
||||||
|
(l/derived refs/workspace-page)))
|
||||||
|
|
||||||
(def ^:private ref:pixel-grid-color
|
(def ^:private ref:pixel-grid-color
|
||||||
(-> (l/key :pixel-grid-color)
|
(-> (l/key :pixel-grid-color)
|
||||||
(l/derived refs/workspace-page)))
|
(l/derived refs/workspace-page)))
|
||||||
@ -36,6 +41,7 @@
|
|||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[]
|
[]
|
||||||
(let [background (mf/deref ref:background-color)
|
(let [background (mf/deref ref:background-color)
|
||||||
|
background-token (mf/deref ref:background-token)
|
||||||
grid-color (mf/deref ref:pixel-grid-color)
|
grid-color (mf/deref ref:pixel-grid-color)
|
||||||
grid-alpha (mf/deref ref:pixel-grid-opacity)
|
grid-alpha (mf/deref ref:pixel-grid-opacity)
|
||||||
|
|
||||||
@ -43,6 +49,17 @@
|
|||||||
on-open (mf/use-fn #(st/emit! (dwu/start-undo-transaction :options)))
|
on-open (mf/use-fn #(st/emit! (dwu/start-undo-transaction :options)))
|
||||||
on-close (mf/use-fn #(st/emit! (dwu/commit-undo-transaction :options)))
|
on-close (mf/use-fn #(st/emit! (dwu/commit-undo-transaction :options)))
|
||||||
|
|
||||||
|
on-token-change
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [_event token]
|
||||||
|
(when-let [color (dwta/value->color (:resolved-value token))]
|
||||||
|
(st/emit! (dw/apply-canvas-color-token (:color color) (:name token))))))
|
||||||
|
|
||||||
|
on-detach-token
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [_token-name]
|
||||||
|
(st/emit! (dw/detach-canvas-color-token))))
|
||||||
|
|
||||||
on-grid-change
|
on-grid-change
|
||||||
(mf/use-fn #(st/emit! (dw/change-pixel-grid-color %)))
|
(mf/use-fn #(st/emit! (dw/change-pixel-grid-color %)))
|
||||||
|
|
||||||
@ -54,7 +71,8 @@
|
|||||||
{:color (d/nilv grid-color clr/default-pixel-grid-color)
|
{:color (d/nilv grid-color clr/default-pixel-grid-color)
|
||||||
:opacity (d/nilv grid-alpha clr/default-pixel-grid-opacity)})]
|
:opacity (d/nilv grid-alpha clr/default-pixel-grid-opacity)})]
|
||||||
|
|
||||||
[:* [:div {:class (stl/css :element-set)}
|
[:* [:section {:class (stl/css :element-set)
|
||||||
|
:aria-label (tr "workspace.options.canvas-background-section")}
|
||||||
[:div {:class (stl/css :element-title)}
|
[:div {:class (stl/css :element-title)}
|
||||||
[:> title-bar* {:collapsable false
|
[:> title-bar* {:collapsable false
|
||||||
:title (tr "workspace.options.canvas-background")
|
:title (tr "workspace.options.canvas-background")
|
||||||
@ -68,6 +86,9 @@
|
|||||||
:title (tr "workspace.options.canvas-background")
|
:title (tr "workspace.options.canvas-background")
|
||||||
:color color
|
:color color
|
||||||
:on-change on-change
|
:on-change on-change
|
||||||
|
:on-token-change on-token-change
|
||||||
|
:on-detach-token on-detach-token
|
||||||
|
:applied-token background-token
|
||||||
:origin :canvas
|
:origin :canvas
|
||||||
:on-open on-open
|
:on-open on-open
|
||||||
:on-close on-close}]]]
|
:on-close on-close}]]]
|
||||||
|
|||||||
@ -6955,6 +6955,10 @@ msgstr "Cancel"
|
|||||||
msgid "workspace.options.canvas-background"
|
msgid "workspace.options.canvas-background"
|
||||||
msgstr "Canvas background"
|
msgstr "Canvas background"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/options/page.cljs:75
|
||||||
|
msgid "workspace.options.canvas-background-section"
|
||||||
|
msgstr "Canvas background section"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:722
|
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:722
|
||||||
msgid "workspace.options.clip-content"
|
msgid "workspace.options.clip-content"
|
||||||
msgstr "Clip content"
|
msgstr "Clip content"
|
||||||
|
|||||||
@ -7021,6 +7021,10 @@ msgstr "Cancelar"
|
|||||||
msgid "workspace.options.canvas-background"
|
msgid "workspace.options.canvas-background"
|
||||||
msgstr "Color de fondo"
|
msgstr "Color de fondo"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/options/page.cljs:75
|
||||||
|
msgid "workspace.options.canvas-background-section"
|
||||||
|
msgstr "Sección de fondo de lienzo"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:722
|
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs:722
|
||||||
msgid "workspace.options.clip-content"
|
msgid "workspace.options.clip-content"
|
||||||
msgstr "Truncar contenido"
|
msgstr "Truncar contenido"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user