mirror of
https://github.com/penpot/penpot.git
synced 2026-09-30 07:46:16 +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
|
||||
;; way to remove already set background
|
||||
[:background {:optional true} [:maybe ctc/schema:hex-color]]
|
||||
[:background-token {:optional true} [:maybe cto/schema:token-name]]
|
||||
[:name {:optional true} :string]
|
||||
;; Pixel grid display controls — nil removes the per-page override
|
||||
;; and falls back to the default hardcoded grid color/opacity.
|
||||
@ -878,6 +879,7 @@
|
||||
(fn [page]
|
||||
(let [name (get params :name)
|
||||
bg (get params :background :not-found)
|
||||
bg-token (get params :background-token :not-found)
|
||||
grid-color (get params :pixel-grid-color :not-found)
|
||||
grid-op (get params :pixel-grid-opacity :not-found)]
|
||||
(cond-> page
|
||||
@ -890,6 +892,12 @@
|
||||
(nil? bg)
|
||||
(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)
|
||||
(assoc :pixel-grid-color grid-color)
|
||||
|
||||
|
||||
@ -242,7 +242,7 @@
|
||||
(let [page (::page (meta changes))]
|
||||
(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)}
|
||||
redo (cond-> change
|
||||
(some? name)
|
||||
@ -251,6 +251,9 @@
|
||||
(some? background)
|
||||
(assoc :background background)
|
||||
|
||||
(contains? options :background-token)
|
||||
(assoc :background-token (:background-token options))
|
||||
|
||||
(some? pixel-grid-color)
|
||||
(assoc :pixel-grid-color pixel-grid-color)
|
||||
|
||||
@ -264,6 +267,9 @@
|
||||
(some? background)
|
||||
(assoc :background (:background page))
|
||||
|
||||
(contains? options :background-token)
|
||||
(assoc :background-token (:background-token page))
|
||||
|
||||
(some? pixel-grid-color)
|
||||
(assoc :pixel-grid-color (:pixel-grid-color page))
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
[app.common.types.grid :as ctg]
|
||||
[app.common.types.plugins :as ctpg]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.uuid :as uuid]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
@ -60,6 +61,8 @@
|
||||
[:guides {:optional true} schema:guides]
|
||||
[:plugin-data {:optional true} ctpg/schema:plugin-data]
|
||||
[: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
|
||||
;; unset so existing files render identically to before.
|
||||
[:pixel-grid-color {:optional true} ctc/schema:hex-color]
|
||||
|
||||
@ -605,6 +605,7 @@
|
||||
:dimensions [:dimensions]
|
||||
:fill [:color]
|
||||
:stroke-color [:color]
|
||||
:canvas [:color]
|
||||
:typography [:typography]
|
||||
:number [:number]
|
||||
:sizing [:sizing :dimensions]
|
||||
|
||||
@ -331,6 +331,32 @@
|
||||
res (ch/process-changes data [chg])]
|
||||
(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
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
|
||||
@ -40,6 +40,32 @@ test.describe("Tokens: Apply token", () => {
|
||||
).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 ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@ -1372,7 +1372,41 @@
|
||||
page (dsh/lookup-page state page-id)
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(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)))))))
|
||||
|
||||
(defn change-pixel-grid-color
|
||||
|
||||
@ -376,7 +376,8 @@
|
||||
:icon i/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
|
||||
(mf/with-effect []
|
||||
|
||||
@ -139,10 +139,10 @@
|
||||
|
||||
on-token-pill-click
|
||||
(mf/use-fn
|
||||
(mf/deps selected-shapes)
|
||||
(mf/deps selected-shapes color-origin)
|
||||
(fn [event token]
|
||||
(dom/stop-propagation event)
|
||||
(when (seq selected-shapes)
|
||||
(when (or (= color-origin :canvas) (seq selected-shapes))
|
||||
(on-token-change event token))))
|
||||
|
||||
create-token-on-set
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.types.color :as clr]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
@ -24,6 +25,10 @@
|
||||
(-> (l/key :background)
|
||||
(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
|
||||
(-> (l/key :pixel-grid-color)
|
||||
(l/derived refs/workspace-page)))
|
||||
@ -36,6 +41,7 @@
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [background (mf/deref ref:background-color)
|
||||
background-token (mf/deref ref:background-token)
|
||||
grid-color (mf/deref ref:pixel-grid-color)
|
||||
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-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
|
||||
(mf/use-fn #(st/emit! (dw/change-pixel-grid-color %)))
|
||||
|
||||
@ -54,7 +71,8 @@
|
||||
{:color (d/nilv grid-color clr/default-pixel-grid-color)
|
||||
: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)}
|
||||
[:> title-bar* {:collapsable false
|
||||
:title (tr "workspace.options.canvas-background")
|
||||
@ -68,6 +86,9 @@
|
||||
:title (tr "workspace.options.canvas-background")
|
||||
:color color
|
||||
:on-change on-change
|
||||
:on-token-change on-token-change
|
||||
:on-detach-token on-detach-token
|
||||
:applied-token background-token
|
||||
:origin :canvas
|
||||
:on-open on-open
|
||||
:on-close on-close}]]]
|
||||
|
||||
@ -6955,6 +6955,10 @@ msgstr "Cancel"
|
||||
msgid "workspace.options.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
|
||||
msgid "workspace.options.clip-content"
|
||||
msgstr "Clip content"
|
||||
|
||||
@ -7021,6 +7021,10 @@ msgstr "Cancelar"
|
||||
msgid "workspace.options.canvas-background"
|
||||
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
|
||||
msgid "workspace.options.clip-content"
|
||||
msgstr "Truncar contenido"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user