diff --git a/.gitignore b/.gitignore index e7dfcc7462..701cbe1e15 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ !.yarn/releases !.yarn/sdks !.yarn/versions +.pnpm-store *-init.clj *.css.json *.jar diff --git a/CHANGES.md b/CHANGES.md index f1fd70b2e5..0e7b48fa22 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ - Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460) - Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339) - Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797) +- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915) ## 2.12.1 diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj index 93330b4e4d..7668a49b99 100644 --- a/backend/src/app/auth/oidc.clj +++ b/backend/src/app/auth/oidc.clj @@ -36,17 +36,6 @@ [integrant.core :as ig] [yetti.response :as-alias yres])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; HELPERS -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn obfuscate-string - [s] - (if (< (count s) 10) - (apply str (take (count s) (repeat "*"))) - (str (subs s 0 5) - (apply str (take (- (count s) 5) (repeat "*")))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; OIDC PROVIDER (GENERIC) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -177,7 +166,7 @@ (l/inf :hint "provider initialized" :provider (:id provider) :client-id (:client-id provider) - :client-secret (obfuscate-string (:client-secret provider))) + :client-secret (d/obfuscate-string (:client-secret provider))) provider) (catch Throwable cause @@ -222,7 +211,7 @@ (l/inf :hint "provider initialized" :provider (:id provider) :client-id (:client-id provider) - :client-secret (obfuscate-string (:client-secret provider))) + :client-secret (d/obfuscate-string (:client-secret provider))) provider) (catch Throwable cause @@ -299,7 +288,7 @@ (l/inf :hint "provider initialized" :provider (:id provider) :client-id (:client-id provider) - :client-secret (obfuscate-string (:client-secret provider))) + :client-secret (d/obfuscate-string (:client-secret provider))) provider) (catch Throwable cause @@ -341,7 +330,7 @@ :provider "gitlab" :base-uri (:base-uri provider) :client-id (:client-id provider) - :client-secret (obfuscate-string (:client-secret provider))) + :client-secret (d/obfuscate-string (:client-secret provider))) provider) (catch Throwable cause (ex/raise :type ::internal @@ -361,7 +350,7 @@ (l/inf :hint "provider initialized" :provider (:id provider) :client-id (:client-id provider) - :client-secret (obfuscate-string (:client-secret provider))) + :client-secret (d/obfuscate-string (:client-secret provider))) provider) (catch Throwable cause @@ -459,7 +448,7 @@ (l/trc :hint "fetch access token" :provider (:id provider) :client-id (:client-id provider) - :client-secret (obfuscate-string (:client-secret provider)) + :client-secret (d/obfuscate-string (:client-secret provider)) :grant-type (:grant_type params) :redirect-uri (:redirect_uri params)) @@ -512,7 +501,7 @@ [cfg provider tdata] (l/trc :hint "fetch user info" :uri (:user-uri provider) - :token (obfuscate-string (:token/access tdata))) + :token (d/obfuscate-string (:token/access tdata))) (let [params {:uri (:user-uri provider) :headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))} diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 015246c005..6f05c439da 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -1024,6 +1024,26 @@ :clj (sort comp-fn items)))) +(defn obfuscate-string + "Obfuscates potentially sensitive values. + + - One-arg arity: + * For strings shorter than 10 characters, all characters are replaced by `*`. + * For longer strings, the first 5 characters are preserved and the rest obfuscated. + - Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value + by `*`, preserving only the length." + ([v] + (obfuscate-string v false)) + ([v full?] + (let [s (str v) + n (count s)] + (cond + (zero? n) s + full? (apply str (repeat n "*")) + (< n 10) (apply str (repeat n "*")) + :else (str (subs s 0 5) + (apply str (repeat (- n 5) "*"))))))) + (defn reorder "Reorder a vector by moving one of their items from some position to some space between positions. It clamps the position numbers to a valid range." diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index 6def4ef91c..8a0a9cf834 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -10,6 +10,7 @@ (:refer-clojure :exclude [instance?]) (:require #?(:clj [clojure.stacktrace :as strace]) + [app.common.data :refer [obfuscate-string]] [app.common.pprint :as pp] [app.common.schema :as sm] [clojure.core :as c] @@ -19,6 +20,10 @@ (:import clojure.lang.IPersistentMap))) +(def ^:private sensitive-fields + "Keys whose values must be obfuscated in validation explains." + #{:password :old-password :token :invitation-token}) + #?(:clj (set! *warn-on-reflection* true)) (def ^:dynamic *data-length* 8) @@ -110,7 +115,25 @@ (explain (:explain data) opts) (contains? data ::sm/explain) - (sm/humanize-explain (::sm/explain data) opts))) + (let [exp (::sm/explain data) + sanitize-map (fn sanitize-map [m] + (reduce-kv + (fn [acc k v] + (let [k* (if (string? k) (keyword k) k)] + (cond + (contains? sensitive-fields k*) + (assoc acc k (if (map? v) + (sanitize-map v) + (obfuscate-string v true))) + + (map? v) (assoc acc k (sanitize-map v)) + :else (assoc acc k v)))) + {} + m)) + sanitize-explain (fn [exp] + (cond-> exp + (:value exp) (update :value sanitize-map)))] + (sm/humanize-explain (sanitize-explain exp) opts)))) #?(:clj (defn format-throwable diff --git a/common/src/app/common/path_names.cljc b/common/src/app/common/path_names.cljc index 74774c0044..6fdf1d1525 100644 --- a/common/src/app/common/path_names.cljc +++ b/common/src/app/common/path_names.cljc @@ -132,94 +132,3 @@ Some naming conventions: (if-let [last-period (str/last-index-of s ".")] [(subs s 0 (inc last-period)) (subs s (inc last-period))] [s ""])) - -;; Tree building functions -------------------------------------------------- - -"Build tree structure from flat list of paths" - -"`build-tree-root` is the main function to build the tree." - -"Receives a list of segments with 'name' properties representing paths, - and a separator string." -"E.g segments = [{... :name 'one/two/three'} {... :name 'one/two/four'} {... :name 'one/five'}]" - -"Transforms into a tree structure like: - [{:name 'one' - :path 'one' - :depth 0 - :leaf nil - :children-fn (fn [] [{:name 'two' - :path 'one.two' - :depth 1 - :leaf nil - :children-fn (fn [] [{... :name 'three'} {... :name 'four'}])} - {:name 'five' - :path 'one.five' - :depth 1 - :leaf {... :name 'five'} - ...}])}]" - -(defn- sort-by-children - "Sorts segments so that those with children come first." - [segments separator] - (sort-by (fn [segment] - (let [path (split-path (:name segment) :separator separator) - path-length (count path)] - (if (= path-length 1) - 1 - 0))) - segments)) - -(defn- group-by-first-segment - "Groups segments by their first path segment and update segment name." - [segments separator] - (reduce (fn [acc segment] - (let [[first-segment & remaining-segments] (split-path (:name segment) :separator separator) - rest-path (when (seq remaining-segments) (join-path remaining-segments :separator separator :with-spaces? false))] - (update acc first-segment (fnil conj []) - (if rest-path - (assoc segment :name rest-path) - segment)))) - {} - segments)) - -(defn- sort-and-group-segments - "Sorts elements and groups them by their first path segment." - [segments separator] - (let [sorted (sort-by-children segments separator) - grouped (group-by-first-segment sorted separator)] - grouped)) - -(defn- build-tree-node - "Builds a single tree node with lazy children." - [segment-name remaining-segments separator parent-path depth] - (let [current-path (if parent-path - (str parent-path "." segment-name) - segment-name) - - is-leaf? (and (seq remaining-segments) - (every? (fn [segment] - (let [remaining-segment-name (first (split-path (:name segment) :separator separator))] - (= segment-name remaining-segment-name))) - remaining-segments)) - - leaf-segment (when is-leaf? (first remaining-segments)) - node {:name segment-name - :path current-path - :depth depth - :leaf leaf-segment - :children-fn (when-not is-leaf? - (fn [] - (let [grouped-elements (sort-and-group-segments remaining-segments separator)] - (mapv (fn [[child-segment-name remaining-child-segments]] - (build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth))) - grouped-elements))))}] - node)) - -(defn build-tree-root - "Builds the root level of the tree." - [segments separator] - (let [grouped-elements (sort-and-group-segments segments separator)] - (mapv (fn [[segment-name remaining-segments]] - (build-tree-node segment-name remaining-segments separator nil 0)) - grouped-elements))) diff --git a/common/src/app/common/types/path.cljc b/common/src/app/common/types/path.cljc index b47eca63c0..be0e15b1aa 100644 --- a/common/src/app/common/types/path.cljc +++ b/common/src/app/common/types/path.cljc @@ -112,8 +112,10 @@ (:c2y params) (update-in [index :params :c2y] + (:c2y params))) content))] - (impl/path-data - (reduce apply-to-index (vec content) modifiers)))) + (if (some? modifiers) + (impl/path-data + (reduce apply-to-index (vec content) modifiers)) + content))) (defn transform-content "Applies a transformation matrix over content and returns a new diff --git a/docker/devenv/files/start-tmux.sh b/docker/devenv/files/start-tmux.sh index 6f1716bed9..6418e0a86b 100755 --- a/docker/devenv/files/start-tmux.sh +++ b/docker/devenv/files/start-tmux.sh @@ -8,14 +8,10 @@ source ~/.bashrc echo "[start-tmux.sh] Installing node dependencies" pushd ~/penpot/frontend/ -corepack install; -yarn install; -yarn playwright install chromium +./scripts/setup; popd pushd ~/penpot/exporter/ -corepack install; -yarn install -yarn playwright install chromium +./scripts/setup; popd tmux -2 new-session -d -s penpot diff --git a/exporter/scripts/setup b/exporter/scripts/setup new file mode 100755 index 0000000000..691c8c8d48 --- /dev/null +++ b/exporter/scripts/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e; + +corepack enable; +corepack install; +yarn install; +yarn playwright install chromium diff --git a/frontend/playwright/ui/specs/inspect-tab.spec.js b/frontend/playwright/ui/specs/inspect-tab.spec.js index 12f56f1343..00f481cdab 100644 --- a/frontend/playwright/ui/specs/inspect-tab.spec.js +++ b/frontend/playwright/ui/specs/inspect-tab.spec.js @@ -305,7 +305,7 @@ test.describe("Inspect tab - Styles", () => { ); await openInspectTab(workspacePage); - const panel = await getPanelByTitle(workspacePage, "Size & position"); + const panel = await getPanelByTitle(workspacePage, "Size and position"); await expect(panel).toBeVisible(); const propertyRow = panel.getByTestId("property-row"); @@ -335,7 +335,7 @@ test.describe("Inspect tab - Styles", () => { ); await openInspectTab(workspacePage); - const panel = await getPanelByTitle(workspacePage, "Size & position"); + const panel = await getPanelByTitle(workspacePage, "Size and position"); await expect(panel).toBeVisible(); const propertyRow = panel.getByTestId("property-row"); @@ -375,7 +375,7 @@ test.describe("Inspect tab - Styles", () => { ); await openInspectTab(workspacePage); - const panel = await getPanelByTitle(workspacePage, "Size & position"); + const panel = await getPanelByTitle(workspacePage, "Size and position"); await expect(panel).toBeVisible(); const propertyRow = panel.getByTestId("property-row"); diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js index 3ad2dd2b67..f8502b5ecb 100644 --- a/frontend/playwright/ui/specs/tokens.spec.js +++ b/frontend/playwright/ui/specs/tokens.spec.js @@ -40,7 +40,6 @@ const setupEmptyTokensFile = async (page, options = {}) => { tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal, tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar, tokenSetItems: workspacePage.tokenSetItems, - tokensSidebar: workspacePage.tokensSidebar, tokenSetGroupItems: workspacePage.tokenSetGroupItems, tokenContextMenuForSet: workspacePage.tokenContextMenuForSet, }; @@ -111,12 +110,15 @@ const checkInputFieldWithError = async ( ).toBeVisible(); }; -const checkInputFieldWithoutError = async (inputLocator) => { +const checkInputFieldWithoutError = async ( + tokenThemeUpdateCreateModal, + inputLocator, +) => { expect(await inputLocator.getAttribute("aria-invalid")).toBeNull(); expect(await inputLocator.getAttribute("aria-describedby")).toBeNull(); }; -const testTokenCreationFlow = async ( +async function testTokenCreationFlow( page, { tokenLabel, @@ -130,7 +132,7 @@ const testTokenCreationFlow = async ( resolvedValueText, secondResolvedValueText, }, -) => { +) { const invalidValueError = "Invalid token value"; const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; @@ -240,45 +242,7 @@ const testTokenCreationFlow = async ( await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); -}; - -const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => { - const tokenSegments = tokenName.split("."); - const tokenFolderTree = tokenSegments.slice(0, -1); - const tokenLeafName = tokenSegments.pop(); - - const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`); - const typeSectionButton = typeParentWrapper - .getByRole("button", { - name: type, - }) - .first(); - - const isSectionExpanded = - await typeSectionButton.getAttribute("aria-expanded"); - - if (isSectionExpanded === "false") { - await typeSectionButton.click(); - } - - for (const segment of tokenFolderTree) { - const segmentButton = typeParentWrapper - .getByRole("listitem") - .getByRole("button", { name: segment }) - .first(); - - const isExpanded = await segmentButton.getAttribute("aria-expanded"); - if (isExpanded === "false") { - await segmentButton.click(); - } - } - - await expect( - typeParentWrapper.getByRole("button", { - name: tokenLeafName, - }), - ).toBeEnabled(); -}; +} test.describe("Tokens: Tokens Tab", () => { test("Clicking tokens tab button opens tokens sidebar tab", async ({ @@ -434,12 +398,15 @@ test.describe("Tokens: Tokens Tab", () => { const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; - const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } = + const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFile(page); - await tokensSidebar - .getByRole("button", { name: "Add Token: Color" }) - .click(); + const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + const addTokenButton = tokensTabPanel.getByRole("button", { + name: `Add Token: Color`, + }); + + await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks @@ -504,34 +471,38 @@ test.describe("Tokens: Tokens Tab", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); - await unfoldTokenTree(tokensSidebar, "color", "color.primary"); + await expect( + tokensTabPanel.getByRole("button", { + name: "color.primary", + }), + ).toBeEnabled(); // Create token referencing the previous one with keyboard - await tokensSidebar + await tokensTabPanel .getByRole("button", { name: "Add Token: Color" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); await nameField.click(); - await nameField.fill("secondary"); + await nameField.fill("color.secondary"); await nameField.press("Tab"); await valueField.click(); await valueField.fill("{color.primary}"); await expect(submitButton).toBeEnabled(); - await submitButton.press("Enter"); + await nameField.press("Enter"); await expect( - tokensSidebar.getByRole("button", { - name: "secondary", + tokensTabPanel.getByRole("button", { + name: "color.secondary", }), ).toBeEnabled(); // Tokens tab panel should have two tokens with the color red / #ff0000 await expect( - tokensSidebar.getByRole("button", { name: "#ff0000" }), + tokensTabPanel.getByRole("button", { name: "#ff0000" }), ).toHaveCount(2); // Global set has been auto created and is active @@ -547,7 +518,7 @@ test.describe("Tokens: Tokens Tab", () => { ).toHaveAttribute("aria-checked", "true"); // Check color picker - await tokensSidebar + await tokensTabPanel .getByRole("button", { name: "Add Token: Color" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); @@ -1108,7 +1079,7 @@ test.describe("Tokens: Tokens Tab", () => { const emptyNameError = "Name should be at least 1 character"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = - await setupEmptyTokensFile(page, { flags: ["enable-token-shadow"] }); + await setupEmptyTokensFile(page, {flags: ["enable-token-shadow"]}); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); @@ -1536,15 +1507,24 @@ test.describe("Tokens: Tokens Tab", () => { test("User edits token and auto created set show up in the sidebar", async ({ page, }) => { - const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } = - await setupTokensFile(page); + const { + workspacePage, + tokensUpdateCreateModal, + tokenThemesSetsSidebar, + tokensSidebar, + tokenContextMenuForToken, + } = await setupTokensFile(page); await expect(tokensSidebar).toBeVisible(); - await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + const tokensColorGroup = tokensSidebar.getByRole("button", { + name: "Color 92", + }); + await expect(tokensColorGroup).toBeVisible(); + await tokensColorGroup.click(); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); await colorToken.click({ button: "right" }); @@ -1561,10 +1541,8 @@ test.describe("Tokens: Tokens Tab", () => { await expect(tokensUpdateCreateModal).not.toBeVisible(); - await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed"); - const colorTokenChanged = tokensSidebar.getByRole("button", { - name: "changed", + name: "colors.blue.100.changed", }); await expect(colorTokenChanged).toBeVisible(); }); @@ -1655,10 +1633,11 @@ test.describe("Tokens: Tokens Tab", () => { }); test("User creates grouped color token", async ({ page }) => { - const { workspacePage, tokensUpdateCreateModal, tokensSidebar } = + const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFile(page); - await tokensSidebar + const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + await tokensTabPanel .getByRole("button", { name: "Add Token: Color" }) .click(); @@ -1670,7 +1649,7 @@ test.describe("Tokens: Tokens Tab", () => { const valueField = tokensUpdateCreateModal.getByLabel("Value"); await nameField.click(); - await nameField.fill("dark.primary"); + await nameField.fill("color.dark.primary"); await valueField.click(); await valueField.fill("red"); @@ -1681,9 +1660,7 @@ test.describe("Tokens: Tokens Tab", () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); - await unfoldTokenTree(tokensSidebar, "color", "dark.primary"); - - await expect(tokensSidebar.getByLabel("primary")).toBeEnabled(); + await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled(); }); test("User cant create regular token with value missing", async ({ @@ -1699,6 +1676,7 @@ test.describe("Tokens: Tokens Tab", () => { await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); + const valueField = tokensUpdateCreateModal.getByLabel("Value"); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); @@ -1708,7 +1686,7 @@ test.describe("Tokens: Tokens Tab", () => { // Fill in name but leave value empty await nameField.click(); - await nameField.fill("primary"); + await nameField.fill("color.primary"); // Submit button should remain disabled when value is empty await expect(submitButton).toBeDisabled(); @@ -1726,6 +1704,7 @@ test.describe("Tokens: Tokens Tab", () => { .click(); await expect(tokensUpdateCreateModal).toBeVisible(); + const nameField = tokensUpdateCreateModal.getByLabel("Name"); const valueField = tokensUpdateCreateModal.getByLabel("Value"); await valueField.click(); @@ -1775,10 +1754,15 @@ test.describe("Tokens: Tokens Tab", () => { await expect(tokensSidebar).toBeVisible(); - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + const tokensColorGroup = tokensSidebar.getByRole("button", { + name: "Color 92", + }); + + await expect(tokensColorGroup).toBeVisible(); + await tokensColorGroup.click(); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await colorToken.click({ button: "right" }); @@ -1798,10 +1782,15 @@ test.describe("Tokens: Tokens Tab", () => { await expect(tokensSidebar).toBeVisible(); - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); + const tokensColorGroup = tokensSidebar.getByRole("button", { + name: "Color 92", + }); + await expect(tokensColorGroup).toBeVisible(); + + await tokensColorGroup.click(); const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); await colorToken.click({ button: "right" }); @@ -1814,7 +1803,8 @@ test.describe("Tokens: Tokens Tab", () => { }); test("User fold/unfold color tokens", async ({ page }) => { - const { tokensSidebar } = await setupTokensFile(page); + const { tokensSidebar, tokenContextMenuForToken } = + await setupTokensFile(page); await expect(tokensSidebar).toBeVisible(); @@ -1824,10 +1814,8 @@ test.describe("Tokens: Tokens Tab", () => { await expect(tokensColorGroup).toBeVisible(); await tokensColorGroup.click(); - unfoldTokenTree(tokensSidebar, "color", "colors.blue.100"); - const colorToken = tokensSidebar.getByRole("button", { - name: "100", + name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); await tokensColorGroup.click(); @@ -2230,10 +2218,13 @@ test.describe("Tokens: Apply token", () => { const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); await tokensTabButton.click(); - unfoldTokenTree(tokensSidebar, "color", "colors.black"); + await tokensSidebar + .getByRole("button") + .filter({ hasText: "Color" }) + .click(); await tokensSidebar - .getByRole("button", { name: "black" }) + .getByRole("button", { name: "colors.black" }) .click({ button: "right" }); await tokenContextMenuForToken.getByText("Fill").click(); @@ -2471,7 +2462,7 @@ test.describe("Tokens: Apply token", () => { await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); - await nameField.fill("primary"); + await nameField.fill("shadow.primary"); // User adds first shadow with a color from the color ramp const firstShadowFields = tokensUpdateCreateModal.getByTestId( @@ -2718,11 +2709,9 @@ test.describe("Tokens: Apply token", () => { await submitButton.click(); await expect(tokensUpdateCreateModal).not.toBeVisible(); - unfoldTokenTree(tokensSidebar, "shadow", "primary"); - // Verify token appears in sidebar const shadowToken = tokensSidebar.getByRole("button", { - name: "primary", + name: "shadow.primary", }); await expect(shadowToken).toBeEnabled(); diff --git a/frontend/scripts/setup b/frontend/scripts/setup new file mode 100755 index 0000000000..7c7014e54d --- /dev/null +++ b/frontend/scripts/setup @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +corepack enable; +corepack install; +yarn install; +yarn playwright install chromium; diff --git a/frontend/scripts/test-components b/frontend/scripts/test-components index 5e7676c1e7..747a417780 100755 --- a/frontend/scripts/test-components +++ b/frontend/scripts/test-components @@ -1,10 +1,10 @@ #!/usr/bin/env bash -set -ex -corepack enable; -corepack install; -yarn install; +SCRIPT_DIR=$(dirname $0); + +set -ex + +$SCRIPT_DIR/setup; -yarn run playwright install chromium --with-deps; yarn run build:storybook yarn run test:storybook diff --git a/frontend/scripts/test-e2e b/frontend/scripts/test-e2e index 8accfcced5..1903b4ff18 100755 --- a/frontend/scripts/test-e2e +++ b/frontend/scripts/test-e2e @@ -1,8 +1,9 @@ #!/usr/bin/env bash +SCRIPT_DIR=$(dirname $0); + set -ex -corepack enable; -corepack install; -yarn install; -yarn run playwright install chromium --with-deps; + +$SCRIPT_DIR/setup; + yarn run test:e2e -x --workers=2 --reporter=list "$@"; diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 8839947701..1210111f71 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -47,32 +47,31 @@ (ptk/reify ::apply-content-modifiers ptk/WatchEvent (watch [it state _] - (let [page-id (get state :current-page-id state) - objects (dsh/lookup-page-objects state) - - id (st/get-path-id state) - - shape - (st/get-path state) + (let [id (st/get-path-id state) + shape (st/get-path state) content-modifiers - (dm/get-in state [:workspace-local :edit-path id :content-modifiers]) + (dm/get-in state [:workspace-local :edit-path id :content-modifiers])] + (if (or (nil? shape) (nil? content-modifiers)) + (rx/of (dwe/clear-edition-mode)) + (let [page-id (get state :current-page-id state) + objects (dsh/lookup-page-objects state) - content (get shape :content) - new-content (path/apply-content-modifiers content content-modifiers) + content (get shape :content) + new-content (path/apply-content-modifiers content content-modifiers) - old-points (path.segment/get-points content) - new-points (path.segment/get-points new-content) - point-change (->> (map hash-map old-points new-points) (reduce merge))] + old-points (path.segment/get-points content) + new-points (path.segment/get-points new-content) + point-change (->> (map hash-map old-points new-points) (reduce merge))] - (when (and (some? new-content) (some? shape)) - (let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)] - (if (empty? new-content) - (rx/of (dch/commit-changes changes) - (dwe/clear-edition-mode)) - (rx/of (dch/commit-changes changes) - (selection/update-selection point-change) - (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler)))))))))) + (when (and (some? new-content) (some? shape)) + (let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)] + (if (empty? new-content) + (rx/of (dch/commit-changes changes) + (dwe/clear-edition-mode)) + (rx/of (dch/commit-changes changes) + (selection/update-selection point-change) + (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler)))))))))))) (defn modify-content-point [content {dx :x dy :y} modifiers point] diff --git a/frontend/src/app/main/ui/components/title_bar.cljs b/frontend/src/app/main/ui/components/title_bar.cljs index 9c63312590..432936b0b3 100644 --- a/frontend/src/app/main/ui/components/title_bar.cljs +++ b/frontend/src/app/main/ui/components/title_bar.cljs @@ -53,6 +53,6 @@ (mf/defc inspect-title-bar* - [{:keys [class title]}] + [{:keys [class title title-class]}] [:div {:class [(stl/css :title-bar) class]} - [:div {:class (stl/css :title-only :inspect-title)} title]]) + [:div {:class [title-class (stl/css :title-only :inspect-title)]} title]]) diff --git a/frontend/src/app/main/ui/ds/layers/layer_button.cljs b/frontend/src/app/main/ui/ds/layers/layer_button.cljs deleted file mode 100644 index 759952c30a..0000000000 --- a/frontend/src/app/main/ui/ds/layers/layer_button.cljs +++ /dev/null @@ -1,49 +0,0 @@ -;; 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.ds.layers.layer-button - (:require-macros - [app.main.style :as stl]) - (:require - [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]] - [rumext.v2 :as mf])) - -(def ^:private schema:layer-button - [:map - [:label :string] - [:description {:optional true} [:maybe :string]] - [:class {:optional true} :string] - [:expandable {:optional true} :boolean] - [:expanded {:optional true} :boolean] - [:icon {:optional true} :string] - [:on-toggle-expand fn?]]) - -(mf/defc layer-button* - {::mf/schema schema:layer-button} - [{:keys [label description class is-expandable expanded icon on-toggle-expand children] :rest props}] - (let [button-props (mf/spread-props props - {:class [class (stl/css-case :layer-button true - :layer-button--expandable is-expandable - :layer-button--expanded expanded)] - :type "button" - :on-click on-toggle-expand})] - [:div {:class (stl/css :layer-button-wrapper)} - [:> "button" button-props - [:div {:class (stl/css :layer-button-content)} - (when is-expandable - (if expanded - [:> icon* {:icon-id i/arrow-down :class (stl/css :folder-node-icon)}] - [:> icon* {:icon-id i/arrow-right :class (stl/css :folder-node-icon)}])) - (when icon - [:> icon* {:icon-id icon :class (stl/css :layer-button-icon)}]) - [:span {:class (stl/css :layer-button-name)} - label] - (when description - [:span {:class (stl/css :layer-button-description)} - description]) - [:span {:class (stl/css :layer-button-quantity)}]]] - [:div {:class (stl/css :layer-button-actions)} - children]])) diff --git a/frontend/src/app/main/ui/ds/layers/layer_button.scss b/frontend/src/app/main/ui/ds/layers/layer_button.scss deleted file mode 100644 index 56e59e8acf..0000000000 --- a/frontend/src/app/main/ui/ds/layers/layer_button.scss +++ /dev/null @@ -1,56 +0,0 @@ -// 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 "ds/_borders.scss" as *; -@use "ds/_sizes.scss" as *; -@use "ds/typography.scss" as *; -@use "ds/colors.scss" as *; - -.layer-button-wrapper { - --layer-button-block-size: #{$sz-32}; - --layer-button-background: var(--color-background-primary); - --layer-button-text: var(--color-foreground-secondary); - - display: flex; - justify-content: space-between; - - block-size: var(--layer-button-block-size); - - background: var(--layer-button-background); - color: var(--layer-button-text); -} - -.layer-button { - @include use-typography("body-small"); - - appearance: none; - - flex: 1; - display: flex; - align-items: center; - - border: none; - background: none; - color: inherit; -} - -.layer-button--expanded { - & .layer-button-name { - color: var(--color-foreground-primary); - } -} - -.layer-button-content { - display: flex; - align-items: center; - gap: var(--sp-xs); -} - -.layer-button-description { - padding: var(--sp-xs); - background-color: var(--color-background-tertiary); - border-radius: $br-6; -} diff --git a/frontend/src/app/main/ui/inspect/attributes.scss b/frontend/src/app/main/ui/inspect/attributes.scss index 7735b10010..4eafa389eb 100644 --- a/frontend/src/app/main/ui/inspect/attributes.scss +++ b/frontend/src/app/main/ui/inspect/attributes.scss @@ -12,14 +12,17 @@ flex-direction: column; gap: var(--sp-l); width: 100%; - height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value + max-height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value padding-top: var(--sp-s); + padding-inline: var(--sp-m); overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable; + background-color: var(--low-emphasis-background); } .workspace-element-options { - height: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value + max-height: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value padding-inline: var(--sp-m); + background-color: var(--low-emphasis-background); } diff --git a/frontend/src/app/main/ui/inspect/attributes/blur.cljs b/frontend/src/app/main/ui/inspect/attributes/blur.cljs index 979eeaf0fb..21a21a6b4b 100644 --- a/frontend/src/app/main/ui/inspect/attributes/blur.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/blur.cljs @@ -24,7 +24,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "inspect.attributes.blur") - :class (stl/css :title-spacing-blur)} + :class (stl/css :title-wrapper) + :title-class (stl/css :blur-attr-title)} (when (= (count shapes) 1) [:> copy-button* {:data (css/get-css-property objects (first shapes) :filter) :class (stl/css :copy-btn-title)}])] diff --git a/frontend/src/app/main/ui/inspect/attributes/blur.scss b/frontend/src/app/main/ui/inspect/attributes/blur.scss index 736ab135c9..9ae8c464eb 100644 --- a/frontend/src/app/main/ui/inspect/attributes/blur.scss +++ b/frontend/src/app/main/ui/inspect/attributes/blur.scss @@ -5,17 +5,28 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-blur { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.blur-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .blur-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -23,5 +34,5 @@ } .copy-btn-title { - max-width: deprecated.$s-28; + max-inline-size: $sz-28; } diff --git a/frontend/src/app/main/ui/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/inspect/attributes/fill.cljs index e408859c4f..7c3ea85640 100644 --- a/frontend/src/app/main/ui/inspect/attributes/fill.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/fill.cljs @@ -68,7 +68,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "inspect.attributes.fill") - :class (stl/css :title-spacing-fill)}] + :class (stl/css :title-wrapper) + :class-title (stl/css :fill-attr-title)}] [:div {:class (stl/css :attributes-content)} (for [shape shapes] diff --git a/frontend/src/app/main/ui/inspect/attributes/fill.scss b/frontend/src/app/main/ui/inspect/attributes/fill.scss index c55c401b98..3cede83d81 100644 --- a/frontend/src/app/main/ui/inspect/attributes/fill.scss +++ b/frontend/src/app/main/ui/inspect/attributes/fill.scss @@ -5,16 +5,30 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-fill { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.fill-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .attributes-content { display: grid; gap: deprecated.$s-4; } + +.attributes-fill-block { + block-size: $sz-36; +} diff --git a/frontend/src/app/main/ui/inspect/attributes/geometry.cljs b/frontend/src/app/main/ui/inspect/attributes/geometry.cljs index 78828abdba..52d765fb86 100644 --- a/frontend/src/app/main/ui/inspect/attributes/geometry.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/geometry.cljs @@ -44,7 +44,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "inspect.attributes.size") - :class (stl/css :title-spacing-geometry)} + :class (stl/css :title-wrapper) + :title-class (stl/css :geometry-attr-title)} (when (= (count shapes) 1) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) diff --git a/frontend/src/app/main/ui/inspect/attributes/geometry.scss b/frontend/src/app/main/ui/inspect/attributes/geometry.scss index dd002ce5e6..f1a90db1e3 100644 --- a/frontend/src/app/main/ui/inspect/attributes/geometry.scss +++ b/frontend/src/app/main/ui/inspect/attributes/geometry.scss @@ -5,17 +5,28 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-geometry { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.geometry-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .geometry-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -23,5 +34,5 @@ } .copy-btn-title { - max-width: deprecated.$s-28; + max-inline-size: $sz-28; } diff --git a/frontend/src/app/main/ui/inspect/attributes/layout.cljs b/frontend/src/app/main/ui/inspect/attributes/layout.cljs index 545efdc9fa..e8e4c597ca 100644 --- a/frontend/src/app/main/ui/inspect/attributes/layout.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/layout.cljs @@ -57,7 +57,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title "Layout" - :class (stl/css :title-spacing-layout)} + :class (stl/css :title-wrapper) + :title-class (stl/css :layout-attr-title)} (when (= (count shapes) 1) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) diff --git a/frontend/src/app/main/ui/inspect/attributes/layout.scss b/frontend/src/app/main/ui/inspect/attributes/layout.scss index 0eb1b64168..2164e152fc 100644 --- a/frontend/src/app/main/ui/inspect/attributes/layout.scss +++ b/frontend/src/app/main/ui/inspect/attributes/layout.scss @@ -5,17 +5,28 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-layout { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.layout-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .layout-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -23,5 +34,5 @@ } .copy-btn-title { - max-width: deprecated.$s-28; + max-inline-size: $sz-28; } diff --git a/frontend/src/app/main/ui/inspect/attributes/layout_element.cljs b/frontend/src/app/main/ui/inspect/attributes/layout_element.cljs index 599b8b5cbf..8269329eac 100644 --- a/frontend/src/app/main/ui/inspect/attributes/layout_element.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/layout_element.cljs @@ -69,7 +69,8 @@ [:div {:class (stl/css :attributes-block)} [:> title-bar* {:collapsable false :title menu-title - :class (stl/css :title-spacing-layout-element)} + :class (stl/css :title-wrapper) + :title-class (stl/css :layout-element-attr-title)} (when (= (count shapes) 1) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) :class (stl/css :copy-btn-title)}])] diff --git a/frontend/src/app/main/ui/inspect/attributes/layout_element.scss b/frontend/src/app/main/ui/inspect/attributes/layout_element.scss index 7f32cca1a1..a51009ab53 100644 --- a/frontend/src/app/main/ui/inspect/attributes/layout_element.scss +++ b/frontend/src/app/main/ui/inspect/attributes/layout_element.scss @@ -5,17 +5,28 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-layout-element { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.layout-element-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .layout-element-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -23,5 +34,6 @@ } .copy-btn-title { - max-width: deprecated.$s-28; + max-inline-size: $sz-28; + max-inline-size: $sz-28; } diff --git a/frontend/src/app/main/ui/inspect/attributes/shadow.cljs b/frontend/src/app/main/ui/inspect/attributes/shadow.cljs index 55da3e552f..d64e5be956 100644 --- a/frontend/src/app/main/ui/inspect/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/shadow.cljs @@ -63,7 +63,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "inspect.attributes.shadow") - :class (stl/css :title-spacing-shadow)}] + :class (stl/css :title-wrapper) + :title-class (stl/css :shadow-attr-title)}] [:div {:class (stl/css :attributes-content)} (for [shape shapes] diff --git a/frontend/src/app/main/ui/inspect/attributes/shadow.scss b/frontend/src/app/main/ui/inspect/attributes/shadow.scss index 106cb03bca..8cfb86f0c3 100644 --- a/frontend/src/app/main/ui/inspect/attributes/shadow.scss +++ b/frontend/src/app/main/ui/inspect/attributes/shadow.scss @@ -5,17 +5,28 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-shadow { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.shadow-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .shadow-row { @extend .attr-row; + block-size: $sz-36; } .button-children { diff --git a/frontend/src/app/main/ui/inspect/attributes/stroke.cljs b/frontend/src/app/main/ui/inspect/attributes/stroke.cljs index 11bdfc1655..1592cc5002 100644 --- a/frontend/src/app/main/ui/inspect/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/stroke.cljs @@ -88,7 +88,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "inspect.attributes.stroke") - :class (stl/css :title-spacing-stroke)}] + :class (stl/css :title-wrapper) + :title-class (stl/css :stroke-attr-title)}] [:div {:class (stl/css :attributes-content)} (for [shape shapes] diff --git a/frontend/src/app/main/ui/inspect/attributes/stroke.scss b/frontend/src/app/main/ui/inspect/attributes/stroke.scss index a83ad6cfea..dd5bf8d4b4 100644 --- a/frontend/src/app/main/ui/inspect/attributes/stroke.scss +++ b/frontend/src/app/main/ui/inspect/attributes/stroke.scss @@ -5,21 +5,34 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-stroke { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.stroke-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .attributes-stroke-block { - @include deprecated.flexColumn; + display: flex; + flex-direction: column; + gap: var(--sp-xs); } .stroke-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -28,5 +41,5 @@ .attributes-content { display: grid; - gap: deprecated.$s-4; + gap: var(--sp-xs); } diff --git a/frontend/src/app/main/ui/inspect/attributes/svg.cljs b/frontend/src/app/main/ui/inspect/attributes/svg.cljs index 798a1b0849..27305b4901 100644 --- a/frontend/src/app/main/ui/inspect/attributes/svg.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/svg.cljs @@ -54,5 +54,6 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "workspace.sidebar.options.svg-attrs.title") - :class (stl/css :title-spacing-svg)}] + :class (stl/css :title-wrapper) + :title-class (stl/css :svg-attr-title)}] [:& svg-block {:shape shape}]]))) diff --git a/frontend/src/app/main/ui/inspect/attributes/svg.scss b/frontend/src/app/main/ui/inspect/attributes/svg.scss index 41846547dc..1b7495e61d 100644 --- a/frontend/src/app/main/ui/inspect/attributes/svg.scss +++ b/frontend/src/app/main/ui/inspect/attributes/svg.scss @@ -5,17 +5,29 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/typography.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-svg { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.svg-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .svg-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -23,12 +35,12 @@ } .attributes-subtitle { - @include deprecated.uppercaseTitleTipography; + @include use-typography("headline-small"); display: flex; justify-content: space-between; - height: deprecated.$s-32; + block-size: $sz-32; span { - height: deprecated.$s-32; + block-size: $sz-32; display: flex; align-items: center; } diff --git a/frontend/src/app/main/ui/inspect/attributes/text.cljs b/frontend/src/app/main/ui/inspect/attributes/text.cljs index 0d293bf0ef..33feff476c 100644 --- a/frontend/src/app/main/ui/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/text.cljs @@ -157,7 +157,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (tr "inspect.attributes.typography") - :class (stl/css :title-spacing-text)}] + :class (stl/css :title-wrapper) + :title-class (stl/css :text-atrr-title)}] (for [shape shapes] [:& text-block {:shape shape diff --git a/frontend/src/app/main/ui/inspect/attributes/text.scss b/frontend/src/app/main/ui/inspect/attributes/text.scss index 54bc1e8095..9f3ecf1808 100644 --- a/frontend/src/app/main/ui/inspect/attributes/text.scss +++ b/frontend/src/app/main/ui/inspect/attributes/text.scss @@ -5,23 +5,37 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/_utils.scss" as *; +@use "ds/typography.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-text { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.text-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .attributes-content { - @include deprecated.flexColumn; + display: flex; + flex-direction: column; + gap: var(--sp-xs); } .text-row { @extend .attr-row; - height: unset; - min-height: deprecated.$s-32; + block-size: unset; + min-block-size: $sz-36; :global(.attr-value) { align-items: center; } @@ -32,20 +46,20 @@ } .attributes-content-row { - max-width: deprecated.$s-240; - min-height: calc(deprecated.$s-2 + deprecated.$s-32); - border-radius: deprecated.$br-8; - border: deprecated.$s-1 solid var(--menu-border-color-disabled); - margin-top: deprecated.$s-4; + max-inline-size: px2rem(240); + min-block-size: px2rem(34); + border-radius: $br-8; + border: $b-1 solid var(--menu-border-color-disabled); + margin-block-start: var(--sp-xs); .content { - @include deprecated.bodySmallTypography; + @include use-typography("body-small"); width: 100%; - padding: deprecated.$s-4 0; + padding: var(--sp-xs) 0; color: var(--color-foreground-secondary); } &:hover { - border: deprecated.$s-1 solid var(--color-background-tertiary); + border: $b-1 solid var(--color-background-tertiary); background-color: var(--menu-background-color); .content { color: var(--menu-foreground-color-hover); diff --git a/frontend/src/app/main/ui/inspect/attributes/variant.cljs b/frontend/src/app/main/ui/inspect/attributes/variant.cljs index e9b74dad0f..82b764a606 100644 --- a/frontend/src/app/main/ui/inspect/attributes/variant.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/variant.cljs @@ -42,7 +42,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title (if is-container? (tr "inspect.attributes.variants") (tr "inspect.attributes.variant")) - :class (stl/css :title-spacing-variant)}] + :class (stl/css :title-wrapper) + :title-class (stl/css :variant-attr-title)}] (for [[pos property] (map-indexed vector properties)] [:> variant-block* {:key (dm/str "variant-property-" pos) :name (:name property) :value (:value property)}])])) diff --git a/frontend/src/app/main/ui/inspect/attributes/variant.scss b/frontend/src/app/main/ui/inspect/attributes/variant.scss index 7478fae373..3d0df70402 100644 --- a/frontend/src/app/main/ui/inspect/attributes/variant.scss +++ b/frontend/src/app/main/ui/inspect/attributes/variant.scss @@ -5,18 +5,29 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-variant { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.variant-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .variant-row { @extend .attr-row; - height: fit-content; + block-size: fit-content; + min-block-size: $sz-36; } .button-children { diff --git a/frontend/src/app/main/ui/inspect/attributes/visibility.cljs b/frontend/src/app/main/ui/inspect/attributes/visibility.cljs index b89e165501..af1fc789b9 100644 --- a/frontend/src/app/main/ui/inspect/attributes/visibility.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/visibility.cljs @@ -51,7 +51,8 @@ [:div {:class (stl/css :attributes-block)} [:> inspect-title-bar* {:title "Visibility" - :class (stl/css :title-spacing-visibility)} + :class (stl/css :title-wrapper) + :title-class (stl/css :visibility-attr-title)} (when (= (count shapes) 1) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) diff --git a/frontend/src/app/main/ui/inspect/attributes/visibility.scss b/frontend/src/app/main/ui/inspect/attributes/visibility.scss index d76c906b18..c888735ff1 100644 --- a/frontend/src/app/main/ui/inspect/attributes/visibility.scss +++ b/frontend/src/app/main/ui/inspect/attributes/visibility.scss @@ -5,17 +5,28 @@ // Copyright (c) KALEIDOS INC @use "refactor/common-refactor.scss" as deprecated; +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; .attributes-block { - @include deprecated.flexColumn; + --box-border-color: var(--color-background-primary); + display: flex; + flex-direction: column; + border-block-end: $b-2 solid var(--box-border-color); } -.title-spacing-visibility { - @extend .attr-title; +.title-wrapper { + margin-inline-start: 0; +} + +.visibility-attr-title { + color: var(--entry-foreground-color-hover); + padding-block: var(--sp-s); } .visibility-row { @extend .attr-row; + block-size: $sz-36; } .button-children { @@ -23,5 +34,5 @@ } .copy-btn-title { - max-width: deprecated.$s-28; + max-inline-size: $sz-28; } diff --git a/frontend/src/app/main/ui/inspect/right_sidebar.cljs b/frontend/src/app/main/ui/inspect/right_sidebar.cljs index bcee180956..5e205b502a 100644 --- a/frontend/src/app/main/ui/inspect/right_sidebar.cljs +++ b/frontend/src/app/main/ui/inspect/right_sidebar.cljs @@ -186,6 +186,7 @@ [:> styles-tab* {:color-space color-space :objects objects :shapes shapes + :from from :libraries libraries :file-id file-id}] :computed diff --git a/frontend/src/app/main/ui/inspect/right_sidebar.scss b/frontend/src/app/main/ui/inspect/right_sidebar.scss index e0393254cd..ca57b53f1c 100644 --- a/frontend/src/app/main/ui/inspect/right_sidebar.scss +++ b/frontend/src/app/main/ui/inspect/right_sidebar.scss @@ -24,10 +24,6 @@ } } -.viewer-code { - padding-inline-start: var(--sp-s); -} - .tool-windows { block-size: 100%; display: grid; diff --git a/frontend/src/app/main/ui/inspect/styles.cljs b/frontend/src/app/main/ui/inspect/styles.cljs index 4c46bf393a..72df40bdbe 100644 --- a/frontend/src/app/main/ui/inspect/styles.cljs +++ b/frontend/src/app/main/ui/inspect/styles.cljs @@ -90,7 +90,7 @@ :multiple)) (mf/defc styles-tab* - [{:keys [color-space shapes libraries objects file-id]}] + [{:keys [color-space shapes libraries objects file-id from]}] (let [data (dm/get-in libraries [file-id :data]) first-shape (first shapes) first-component (ctkl/get-component data (:component-id first-shape)) @@ -131,7 +131,8 @@ (mf/deps shorthands*) (fn [shorthand] (swap! shorthands* assoc (:panel shorthand) (:property shorthand))))] - [:ol {:class (stl/css :styles-tab) :aria-label (tr "labels.styles")} + [:ol {:class (stl/css-case :styles-tab true + :styles-tab-workspace (= from :workspace)) :aria-label (tr "labels.styles")} ;; TOKENS PANEL (when (or (seq active-themes) (seq active-sets)) [:li diff --git a/frontend/src/app/main/ui/inspect/styles.scss b/frontend/src/app/main/ui/inspect/styles.scss index 1c1c4e6871..0680351132 100644 --- a/frontend/src/app/main/ui/inspect/styles.scss +++ b/frontend/src/app/main/ui/inspect/styles.scss @@ -7,5 +7,9 @@ @use "ds/_utils.scss" as *; .styles-tab { - block-size: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value + block-size: calc(100vh - px2rem(140)); // TODO: Fix this hardcoded value +} + +.styles-tab-workspace { + block-size: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value } diff --git a/frontend/src/app/main/ui/inspect/styles/property_detail_copiable.scss b/frontend/src/app/main/ui/inspect/styles/property_detail_copiable.scss index 413f1437f4..c1ddecdf4e 100644 --- a/frontend/src/app/main/ui/inspect/styles/property_detail_copiable.scss +++ b/frontend/src/app/main/ui/inspect/styles/property_detail_copiable.scss @@ -60,6 +60,7 @@ } .property-detail-text { + @include use-typography("body-small"); color: var(--detail-color); } diff --git a/frontend/src/app/main/ui/inspect/styles/style_box.cljs b/frontend/src/app/main/ui/inspect/styles/style_box.cljs index 432639ac26..9d63b2a052 100644 --- a/frontend/src/app/main/ui/inspect/styles/style_box.cljs +++ b/frontend/src/app/main/ui/inspect/styles/style_box.cljs @@ -19,7 +19,7 @@ (case type :variant (tr "inspect.tabs.styles.variants-panel") :token (tr "inspect.tabs.styles.token-panel") - :geometry (tr "inspect.tabs.styles.geometry-panel") + :geometry (tr "inspect.attributes.size") :fill (tr "labels.fill") :stroke (tr "labels.stroke") :text (tr "labels.text") diff --git a/frontend/src/app/main/ui/inspect/styles/style_box.scss b/frontend/src/app/main/ui/inspect/styles/style_box.scss index fcb9ac73b7..a55a6b5fc4 100644 --- a/frontend/src/app/main/ui/inspect/styles/style_box.scss +++ b/frontend/src/app/main/ui/inspect/styles/style_box.scss @@ -33,7 +33,6 @@ display: flex; align-items: center; gap: var(--title-gap); - padding-block: var(--title-padding); } .disclosure-button { @@ -52,4 +51,5 @@ @include use-typography("headline-small"); flex: 1; color: var(--title-color); + padding-block: var(--title-padding); } diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index e93327313d..24996169ac 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -148,11 +148,12 @@ (mf/use-fn (mf/deps index prefix is-move) (fn [event] - (dom/stop-propagation event) - (dom/prevent-default event) + (when (dom/left-mouse? event) + (dom/stop-propagation event) + (dom/prevent-default event) - (when ^boolean is-move - (st/emit! (drp/start-move-handler index prefix)))))] + (when ^boolean is-move + (st/emit! (drp/start-move-handler index prefix))))))] [:g.handler {:pointer-events (if ^boolean is-draw "none" "visible")} [:line diff --git a/frontend/src/app/main/ui/workspace/tokens/management.cljs b/frontend/src/app/main/ui/workspace/tokens/management.cljs index 846c112bdb..98bbb080b5 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management.cljs @@ -44,39 +44,6 @@ [(seq (array/sort! empty)) (seq (array/sort! filled))])))) -(mf/defc selected-set-info* - {::mf/private true} - [{:keys [tokens-lib selected-token-set-id]}] - (let [selected-token-set - (mf/with-memo [tokens-lib] - (when selected-token-set-id - (some-> tokens-lib (ctob/get-set selected-token-set-id)))) - - active-token-sets-names - (mf/with-memo [tokens-lib] - (some-> tokens-lib (ctob/get-active-themes-set-names))) - - token-set-active? - (mf/use-fn - (mf/deps active-token-sets-names) - (fn [name] - (contains? active-token-sets-names name)))] - [:div {:class (stl/css :sets-header-container)} - [:> text* {:as "span" - :typography "headline-small" - :class (stl/css :sets-header)} - (tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))] - [:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")} - ;; NOTE: when no set in tokens-lib, the selected-token-set-id - ;; will be `nil`, so for properly hide the inactive message we - ;; check that at least `selected-token-set-id` has a value - (when (and (some? selected-token-set-id) - (not (token-set-active? (ctob/get-name selected-token-set)))) - [:* - [:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}] - [:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)} - (tr "workspace.tokens.inactive-set")]])]])) - (mf/defc tokens-section* {::mf/private true} [{:keys [tokens-lib active-tokens resolved-active-tokens]}] @@ -98,7 +65,9 @@ selected-token-set-id (mf/deref refs/selected-token-set-id) - + selected-token-set + (when selected-token-set-id + (some-> tokens-lib (ctob/get-set selected-token-set-id))) ;; If we have not selected any set explicitly we just ;; select the first one from the list of sets @@ -123,9 +92,15 @@ tokens)] (ctob/group-by-type tokens))) + active-token-sets-names + (mf/with-memo [tokens-lib] + (some-> tokens-lib (ctob/get-active-themes-set-names))) - - + token-set-active? + (mf/use-fn + (mf/deps active-token-sets-names) + (fn [name] + (contains? active-token-sets-names name))) [empty-group filled-group] (mf/with-memo [tokens-by-type] @@ -143,27 +118,34 @@ [:* [:& token-context-menu] - - [:& selected-set-info* {:tokens-lib tokens-lib - :selected-token-set-id selected-token-set-id}] + [:div {:class (stl/css :sets-header-container)} + [:> text* {:as "span" :typography "headline-small" :class (stl/css :sets-header)} (tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))] + [:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")} + ;; NOTE: when no set in tokens-lib, the selected-token-set-id + ;; will be `nil`, so for properly hide the inactive message we + ;; check that at least `selected-token-set-id` has a value + (when (and (some? selected-token-set-id) + (not (token-set-active? (ctob/get-name selected-token-set)))) + [:* + [:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}] + [:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)} + (tr "workspace.tokens.inactive-set")]])]] (for [type filled-group] (let [tokens (get tokens-by-type type)] [:> token-group* {:key (name type) - :tokens tokens - :is-expanded (get open-status type false) + :is-open (get open-status type false) :type type :selected-ids selected :selected-shapes selected-shapes :is-selected-inside-layout is-selected-inside-layout :active-theme-tokens resolved-active-tokens - :tokens-lib tokens-lib - :selected-token-set-id selected-token-set-id}])) + :tokens tokens}])) (for [type empty-group] [:> token-group* {:key (name type) - :tokens [] :type type :selected-shapes selected-shapes - :is-selected-inside-layout is-selected-inside-layout - :active-theme-tokens resolved-active-tokens}])])) + :is-selected-inside-layout :is-selected-inside-layout + :active-theme-tokens resolved-active-tokens + :tokens []}])])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs index 0d038a2324..8dd73d5fce 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/group.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.cljs @@ -8,9 +8,6 @@ (ns app.main.ui.workspace.tokens.management.group (:require-macros [app.main.style :as stl]) (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.types.tokens-lib :as ctob] [app.main.data.modal :as modal] [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.library-edit :as dwtl] @@ -19,70 +16,51 @@ [app.main.ui.context :as ctx] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.foundations.assets.icon :as i] - [app.main.ui.ds.layers.layer-button :refer [layer-button*]] - [app.main.ui.workspace.tokens.management.token-tree :refer [token-tree*]] + [app.main.ui.workspace.sidebar.assets.common :as cmm] + [app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) - (defn token-section-icon [type] (case type - :border-radius i/corner-radius - :color i/drop - :boolean i/boolean-difference - :font-family i/text-font-family - :font-size i/text-font-size - :letter-spacing i/text-letterspacing - :text-case i/text-mixed - :text-decoration i/text-underlined - :font-weight i/text-font-weight - :typography i/text-typography - :opacity i/percentage - :number i/number - :rotation i/rotation - :spacing i/padding-extended - :string i/text-mixed - :stroke-width i/stroke-size - :dimensions i/expand - :sizing i/expand - :shadow i/drop-shadow + :border-radius "corner-radius" + :color "drop" + :boolean "boolean-difference" + :font-family "text-font-family" + :font-size "text-font-size" + :letter-spacing "text-letterspacing" + :text-case "text-mixed" + :text-decoration "text-underlined" + :font-weight "text-font-weight" + :typography "text-typography" + :opacity "percentage" + :number "number" + :rotation "rotation" + :spacing "padding-extended" + :string "text-mixed" + :stroke-width "stroke-size" + :dimensions "expand" + :sizing "expand" + :shadow "drop-shadow" "add")) -(def ^:private schema:token-group - [:map - [:type :keyword] - [:tokens :any] - [:selected-shapes :any] - [:is-selected-inside-layout {:optional true} [:maybe :boolean]] - [:active-theme-tokens {:optional true} :any] - [:selected-token-set-id {:optional true} :any] - [:tokens-lib {:optional true} :any] - [:on-token-pill-click {:optional true} fn?] - [:on-context-menu {:optional true} fn?]]) - (mf/defc token-group* - {::mf/schema schema:token-group} - [{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib is-expanded selected-ids]}] + {::mf/private true} + [{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens is-open selected-ids]}] (let [{:keys [modal title]} (get dwta/token-properties type) editing-ref (mf/deref refs/workspace-editor-state) not-editing? (empty? editing-ref) - is-expanded (d/nilv is-expanded false) - can-edit? (mf/use-ctx ctx/can-edit?) - is-selected-inside-layout (d/nilv is-selected-inside-layout false) - tokens (mf/with-memo [tokens] (vec (sort-by :name tokens))) - expandable? (d/nilv (seq tokens) false) - on-context-menu (mf/use-fn (fn [event token] @@ -95,8 +73,8 @@ on-toggle-open-click (mf/use-fn - (mf/deps is-expanded type) - #(st/emit! (dwtl/set-token-type-section-open type (not is-expanded)))) + (mf/deps is-open type) + #(st/emit! (dwtl/set-token-type-section-open type (not is-open)))) on-popover-open-click (mf/use-fn @@ -118,36 +96,33 @@ (mf/use-fn (mf/deps not-editing? selected-ids) (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)) - (st/emit! (dwta/toggle-token {:token token - :shape-ids selected-ids}))))))] + (dom/stop-propagation event) + (when (and not-editing? (seq selected-shapes) (not= (:type token) :number)) + (st/emit! (dwta/toggle-token {:token token + :shape-ids selected-ids})))))] - [:div {:class (stl/css :token-section-wrapper) - :data-testid (dm/str "section-" (name type))} - [:> layer-button* {:label title - :expanded is-expanded - :description (when expandable? (dm/str (count tokens))) - :is-expandable expandable? - :aria-expanded is-expanded - :aria-controls (dm/str "token-tree-" (name type)) - :on-toggle-expand on-toggle-open-click - :icon (token-section-icon type)} - (when can-edit? - [:> icon-button* {:id (str "add-token-button-" title) - :icon "add" - :aria-label (tr "workspace.tokens.add-token" title) - :variant "ghost" - :on-click on-popover-open-click - :class (stl/css :token-section-icon)}])] - (when is-expanded - [:> token-tree* {:tokens tokens - :id (dm/str "token-tree-" (name type)) - :tokens-lib tokens-lib - :selected-shapes selected-shapes - :active-theme-tokens active-theme-tokens - :selected-token-set-id selected-token-set-id - :is-selected-inside-layout is-selected-inside-layout - :on-token-pill-click on-token-pill-click - :on-context-menu on-context-menu}])])) + [:div {:on-click on-toggle-open-click :class (stl/css :token-section-wrapper)} + [:> cmm/asset-section* {:icon (token-section-icon type) + :title title + :section :tokens + :assets-count (count tokens) + :is-open is-open} + [:> cmm/asset-section-block* {:role :title-button} + (when can-edit? + [:> icon-button* {:on-click on-popover-open-click + :variant "ghost" + :icon i/add + :id (str "add-token-button-" title) + :aria-label (tr "workspace.tokens.add-token" title)}])] + (when is-open + [:> cmm/asset-section-block* {:role :content} + [:div {:class (stl/css :token-pills-wrapper)} + (for [token tokens] + [:> token-pill* + {:key (:name token) + :token token + :selected-shapes selected-shapes + :is-selected-inside-layout is-selected-inside-layout + :active-theme-tokens active-theme-tokens + :on-click on-token-pill-click + :on-context-menu on-context-menu}])]])]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/group.scss b/frontend/src/app/main/ui/workspace/tokens/management/group.scss new file mode 100644 index 0000000000..e46bfb846f --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/management/group.scss @@ -0,0 +1,11 @@ +// 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 + +.token-pills-wrapper { + display: flex; + gap: var(--sp-xs); + flex-wrap: wrap; +} diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs index bfb1a1f0a3..dd001e187c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs @@ -307,9 +307,10 @@ :class (stl/css :token-pill-icon)}]) (if contains-path? - (let [[_ last-part] (cpn/split-by-last-period name)] + (let [[first-part last-part] (cpn/split-by-last-period name)] [:span {:class (stl/css :divided-name-wrapper) :aria-label name} + [:span {:class (stl/css :first-name-wrapper)} first-part] [:span {:class (stl/css :last-name-wrapper)} last-part]]) [:span {:class (stl/css :name-wrapper) :aria-label name} diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs deleted file mode 100644 index 5a31dbcd54..0000000000 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs +++ /dev/null @@ -1,110 +0,0 @@ -;; 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.management.token-tree - (:require-macros [app.main.style :as stl]) - (:require - [app.common.path-names :as cpn] - [app.common.types.tokens-lib :as ctob] - [app.main.ui.ds.layers.layer-button :refer [layer-button*]] - [app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]] - [rumext.v2 :as mf])) - -(def ^:private schema:folder-node - [:map - [:node :any] - [:selected-shapes :any] - [:is-selected-inside-layout {:optional true} :boolean] - [:active-theme-tokens {:optional true} :any] - [:selected-token-set-id {:optional true} :any] - [:tokens-lib {:optional true} :any] - [:on-token-pill-click {:optional true} fn?] - [:on-context-menu {:optional true} fn?]]) - -(mf/defc folder-node* - {::mf/schema schema:folder-node} - [{:keys [node selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib on-token-pill-click on-context-menu]}] - (let [expanded* (mf/use-state false) - expanded (deref expanded*) - swap-folder-expanded #(swap! expanded* not)] - [:li {:class (stl/css :folder-node)} - [:> layer-button* {:label (:name node) - :expanded expanded - :aria-expanded expanded - :aria-controls (str "folder-children-" (:path node)) - :is-expandable (not (:leaf node)) - :on-toggle-expand swap-folder-expanded}] - (when expanded - (let [children-fn (:children-fn node)] - [:div {:class (stl/css :folder-children-wrapper) - :id (str "folder-children-" (:path node))} - (when children-fn - (let [children (children-fn)] - (for [child children] - (if (not (:leaf child)) - [:ul {:class (stl/css :node-parent)} - [:> folder-node* {:key (:path child) - :node child - :selected-shapes selected-shapes - :is-selected-inside-layout is-selected-inside-layout - :active-theme-tokens active-theme-tokens - :on-token-pill-click on-token-pill-click - :on-context-menu on-context-menu - :tokens-lib tokens-lib - :selected-token-set-id selected-token-set-id}]] - (let [id (:id (:leaf child)) - token (ctob/get-token tokens-lib selected-token-set-id id)] - [:> token-pill* - {:key id - :token token - :selected-shapes selected-shapes - :is-selected-inside-layout is-selected-inside-layout - :active-theme-tokens active-theme-tokens - :on-click on-token-pill-click - :on-context-menu on-context-menu}])))))]))])) - -(def ^:private schema:token-tree - [:map - [:tokens :any] - [:selected-shapes :any] - [:is-selected-inside-layout {:optional true} :boolean] - [:active-theme-tokens {:optional true} :any] - [:selected-token-set-id {:optional true} :any] - [:tokens-lib {:optional true} :any] - [:on-token-pill-click {:optional true} fn?] - [:on-context-menu {:optional true} fn?]]) - -(mf/defc token-tree* - {::mf/schema schema:token-tree} - [{:keys [tokens selected-shapes is-selected-inside-layout active-theme-tokens tokens-lib selected-token-set-id on-token-pill-click on-context-menu]}] - (let [separator "." - tree (mf/use-memo - (mf/deps tokens) - (fn [] - (cpn/build-tree-root tokens separator)))] - [:div {:class (stl/css :token-tree-wrapper)} - (for [node tree] - [:ul {:class (stl/css :node-parent) - :key (:path node) - :style {:--node-depth (inc (:depth node))}} - (if (:leaf node) - (let [token (ctob/get-token tokens-lib selected-token-set-id (get-in node [:leaf :id]))] - [:> token-pill* - {:token token - :selected-shapes selected-shapes - :is-selected-inside-layout is-selected-inside-layout - :active-theme-tokens active-theme-tokens - :on-click on-token-pill-click - :on-context-menu on-context-menu}]) - ;; Render segment folder - [:> folder-node* {:node node - :selected-shapes selected-shapes - :is-selected-inside-layout is-selected-inside-layout - :active-theme-tokens active-theme-tokens - :on-token-pill-click on-token-pill-click - :on-context-menu on-context-menu - :tokens-lib tokens-lib - :selected-token-set-id selected-token-set-id}])])])) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.scss b/frontend/src/app/main/ui/workspace/tokens/management/token_tree.scss deleted file mode 100644 index 3320379d04..0000000000 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_tree.scss +++ /dev/null @@ -1,39 +0,0 @@ -// 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 "ds/_borders.scss" as *; - -.token-tree-wrapper { - padding-block-end: var(--sp-s); -} - -.node-parent { - --node-spacing: var(--sp-l); - --node-depth: 0; - - margin-block-end: 0; - padding-inline-start: calc(var(--node-spacing) * var(--node-depth)); -} - -.folder-children-wrapper:has(> button) { - margin-inline-start: var(--sp-s); - padding-inline-start: var(--sp-s); - border-inline-start: $b-2 solid var(--color-background-quaternary); - display: flex; - flex-wrap: wrap; - column-gap: var(--sp-xs); - - & .node-parent { - flex: 1 0 100%; - - &:last-of-type { - margin-block-end: var(--sp-s); - } - } - & .token-pill { - flex: 0 0 auto; - } -} diff --git a/frontend/text-editor/README.md b/frontend/text-editor/README.md index 763f4424a9..705c2277bd 100644 --- a/frontend/text-editor/README.md +++ b/frontend/text-editor/README.md @@ -37,7 +37,7 @@ This command is going to search for the file located in `frontend/src/app/main/u ## How it works? -The text editor divides the content in three elements: `root`, `paragraph` and `inline`. An `inline` in terms of content is a styled element that it is displayed in a line inside a block and an `inline` only can have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `inline`s (**inline** elements). +The text editor divides the content in three elements: `root`, `paragraph` and `textSpan`. In terms of content, a `textSpan` is a styled element displayed on a line within a block. A `textSpan` can only have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `textSpan`s (**textSpan** elements). ```html
@@ -53,10 +53,10 @@ This way we only need to deal with a structure like this, where circular nodes a ```mermaid flowchart TB root((root)) --> paragraph((paragraph)) - paragraph --> inline_1((inline)) - paragraph --> inline_2((inline)) - inline_1 --> text_1[Hello, ] - inline_2 --> text_2[World!] + paragraph --> text_span_1((textSpan)) + paragraph --> text_span_2((textSpan)) + text_span_1 --> text_1[Hello, ] + text_span_2 --> text_2[World!] ``` This is compatible with the way Penpot stores text content. @@ -68,6 +68,26 @@ flowchart TB paragraph --> text((text)) ``` +## How the TextEditor works? + +```mermaid +flowchart TB + TextEditor -->|handles `selectionchange` events| SelectionController + TextEditor -->|handles how the editor dispatches changes| ChangeController +``` + +The `TextEditor` contains a series of references to DOM elements, one of them is a `contenteditable` element that keeps the sub-elements explained before (root, paragraphs and textspans). + +`SelectionController` listens to the `document` event called `selectionchange`. This event is triggered everytime the focus/selection of the browser changes. + +`ChangeController` is called by the `TextEditor` instance everytime a change is performed on the content of the `contenteditable` element. + +### Events + +- `change`: This event is dispatched every time a change is made in the editor. All changes are debounced to prevent dispatching too many change events. This event is also dispatched when there are pending change events and the user blurs the textarea element. + +- `stylechange`: This event is dispatched every time the `currentStyle` changes. This normally happens when the user changes the caret position or the selection and the `currentStyle` is re-computed. + ## How the code is organized? - `editor`: contains everything related to the TextEditor. Where `TextEditor.js` is the main file where all the basic code of the editor is handled. This has been designed so that in the future, when the Web Components API is more stable and has features such as handling selection events within shadow roots we will be able to update this class with little effort.