From eff572d3bb22fb1ca5bf6455df5f5b4c52c9486c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Jan 2026 11:20:44 +0100 Subject: [PATCH] Revert ":lipstick: Group tokens by name path (#7775)" This reverts commit 0956b66281bba6303e2c0acb85bb2aa6be4d9d1a. --- common/src/app/common/path_names.cljc | 91 ---------- frontend/playwright/ui/specs/tokens.spec.js | 157 ++++++++---------- .../app/main/ui/ds/layers/layer_button.cljs | 49 ------ .../app/main/ui/ds/layers/layer_button.scss | 56 ------- .../main/ui/workspace/tokens/management.cljs | 74 ++++----- .../ui/workspace/tokens/management/group.cljs | 133 ++++++--------- .../ui/workspace/tokens/management/group.scss | 11 ++ .../tokens/management/token_pill.cljs | 3 +- .../tokens/management/token_tree.cljs | 110 ------------ .../tokens/management/token_tree.scss | 39 ----- 10 files changed, 168 insertions(+), 555 deletions(-) delete mode 100644 frontend/src/app/main/ui/ds/layers/layer_button.cljs delete mode 100644 frontend/src/app/main/ui/ds/layers/layer_button.scss create mode 100644 frontend/src/app/main/ui/workspace/tokens/management/group.scss delete mode 100644 frontend/src/app/main/ui/workspace/tokens/management/token_tree.cljs delete mode 100644 frontend/src/app/main/ui/workspace/tokens/management/token_tree.scss 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/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/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/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; - } -}