🐛 Token tree must be expanded by default (#8799)

This commit is contained in:
Xaviju 2026-03-30 08:14:17 +02:00 committed by GitHub
parent dff381c4fe
commit 1b68318c6b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 176 additions and 173 deletions

View File

@ -32,6 +32,7 @@
- Fix scroll on library modal [Taiga #13639](https://tree.taiga.io/project/penpot/issue/13639)
- Fix dates to avoid show them in english when browser is in auto [Taiga #13786](https://tree.taiga.io/project/penpot/issue/13786)
- Fix focus radio button [Taiga #13841](https://tree.taiga.io/project/penpot/issue/13841)
- Token tree should be expanded by default [Taiga #13631](https://tree.taiga.io/project/penpot/issue/13631)
## 2.15.0 (Unreleased)

View File

@ -148,16 +148,16 @@ Some naming conventions:
: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'}
...}])}]"
:children [{:name 'two'
:path 'one.two'
:depth 1
:leaf nil
:children [{... :name 'three'} {... :name 'four'}]}
{:name 'five'
:path 'one.five'
:depth 1
:leaf {... :name 'five'}
:children nil}]}]"
(defn- sort-by-children
"Sorts segments so that those with children come first."
@ -191,7 +191,7 @@ Some naming conventions:
(into (sorted-map) grouped)))
(defn- build-tree-node
"Builds a single tree node with lazy children."
"Builds a single tree node with computed children."
[segment-name remaining-segments separator parent-path depth]
(let [current-path (if parent-path
(str parent-path "." segment-name)
@ -208,12 +208,11 @@ Some naming conventions:
: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))))}]
:children (when-not is-leaf?
(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

View File

@ -4,7 +4,7 @@ import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
import {
setupTokensFileRender,
setupTypographyTokensFileRender,
unfoldTokenTree,
unfoldTokenType,
} from "./helpers";
test.beforeEach(async ({ page }) => {
@ -24,10 +24,9 @@ test.describe("Tokens: Apply token", () => {
.filter({ hasText: "Button" })
.click();
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
await page.getByRole("tab", { name: "Tokens" }).click();
unfoldTokenTree(tokensSidebar, "color", "colors.black");
await unfoldTokenType(tokensSidebar, "color");
await tokensSidebar
.getByRole("button", { name: "black" })
@ -52,17 +51,15 @@ test.describe("Tokens: Apply token", () => {
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
// Open tokens sections on left sidebar
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
// Unfold border radius tokens
await page.getByRole("button", { name: "Border Radius 3" }).click();
await page.getByRole("tab", { name: "Tokens" }).click();
await unfoldTokenType(tokensSidebar, "border radius");
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius" }),
).toBeVisible();
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
tokensSidebar.getByRole("button", {
name: "borderRadius.sm",
exact: true,
}),
).toBeVisible();
// Apply border radius token from token panels
@ -119,13 +116,7 @@ test.describe("Tokens: Apply token", () => {
await tokensTabButton.click();
// Unfold opacity tokens
await page.getByRole("button", { name: "Opacity 3" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "opacity", exact: true }),
).toBeVisible();
await tokensSidebar
.getByRole("button", { name: "opacity", exact: true })
.click();
await unfoldTokenType(tokensSidebar, "opacity");
await expect(
tokensSidebar.getByRole("button", { name: "opacity.high" }),
).toBeVisible();
@ -203,12 +194,8 @@ test.describe("Tokens: Apply token", () => {
test("User adds shadow token with multiple shadows and applies it to shape", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
workspacePage,
tokenContextMenuForToken,
} = await setupTokensFileRender(page, { flags: ["enable-token-shadow"] });
const { tokensUpdateCreateModal, tokensSidebar, workspacePage } =
await setupTokensFileRender(page, { flags: ["enable-token-shadow"] });
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
@ -476,8 +463,6 @@ 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",
@ -512,7 +497,7 @@ test.describe("Tokens: Apply token", () => {
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
await unfoldTokenType(tokensSidebar, "dimensions");
// Apply token to width and height token from token panel
await tokensSidebar.getByRole("button", { name: "dimension.sm" }).click();
@ -565,7 +550,7 @@ test.describe("Tokens: Apply token", () => {
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
await unfoldTokenType(tokensSidebar, "dimensions");
// Apply token to width and height token from token panel
await tokensSidebar
@ -621,7 +606,7 @@ test.describe("Tokens: Apply token", () => {
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
await unfoldTokenType(tokensSidebar, "dimensions");
// Apply token to width and height token from token panel
await tokensSidebar
@ -677,7 +662,7 @@ test.describe("Tokens: Apply token", () => {
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.xs");
await unfoldTokenType(tokensSidebar, "dimensions");
// Apply token to width and height token from token panel
await tokensSidebar
@ -809,8 +794,7 @@ test.describe("Tokens: Apply token", () => {
const tokensTab = page.getByRole("tab", { name: "Tokens" });
await expect(tokensTab).toBeVisible();
await tokensTab.click();
await page.getByRole("button", { name: "Dimensions 4" }).click();
await page.getByRole("button", { name: "dim", exact: true }).click();
await unfoldTokenType(workspace.tokensSidebar, "dimensions");
const tokensSidebar = workspace.tokensSidebar;
await expect(
tokensSidebar.getByRole("button", { name: "dim.md" }),
@ -881,11 +865,7 @@ test.describe("Tokens: Detach token", () => {
await tokensTabButton.click();
// Unfold border radius tokens
await page.getByRole("button", { name: "Border Radius 3" }).click();
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius" }),
).toBeVisible();
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
await unfoldTokenType(tokensSidebar, "Border Radius");
await expect(
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
).toBeVisible();

View File

@ -6,7 +6,7 @@ import {
setupTokensFileRender,
setupTypographyTokensFileRender,
testTokenCreationFlow,
unfoldTokenTree,
unfoldTokenType,
} from "./helpers";
test.beforeEach(async ({ page }) => {
@ -31,15 +31,9 @@ test.describe("Tokens - creation", () => {
});
test("User creates border radius token with combobox", async ({ page }) => {
const invalidValueError = "Invalid token value";
const emptyNameError = "Name should be at least 1 character";
const selfReferenceError = "Token has self reference";
const missingReferenceError = "Missing token references";
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFileRender(page, {
flags: ["enable-token-combobox", "enable-feature-token-input"],
});
const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, {
flags: ["enable-token-combobox", "enable-feature-token-input"],
});
// Open modal
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
@ -83,8 +77,10 @@ test.describe("Tokens - creation", () => {
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "border radius");
await expect(
tokensTabPanel.getByRole('button', { name: 'my-token' }),
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
// Create second token referencing the first one using the combobox options
@ -310,7 +306,7 @@ test.describe("Tokens - creation", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "color.primary");
await unfoldTokenType(tokensSidebar, "color");
// Create token referencing the previous one with keyboard
@ -477,6 +473,8 @@ test.describe("Tokens - creation", () => {
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "font family");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
@ -631,6 +629,8 @@ test.describe("Tokens - creation", () => {
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "font weight");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
@ -767,6 +767,8 @@ test.describe("Tokens - creation", () => {
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "text case");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
@ -885,6 +887,8 @@ test.describe("Tokens - creation", () => {
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "text decoration");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
@ -1051,6 +1055,8 @@ test.describe("Tokens - creation", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "shadow");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
@ -1088,6 +1094,8 @@ test.describe("Tokens - creation", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "shadow");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
).toBeEnabled();
@ -1109,7 +1117,9 @@ test.describe("Tokens - creation", () => {
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("typography.empty");
const valueField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" });
const valueField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font Size",
});
// Insert a value and then delete it
await valueField.fill("1");
@ -1274,6 +1284,8 @@ test.describe("Tokens - creation", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenType(tokensTabPanel, "shadow");
await expect(
tokensTabPanel.getByRole("button", { name: "my-token" }),
).toBeEnabled();
@ -1642,7 +1654,7 @@ test.describe("Tokens - creation", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
await unfoldTokenType(tokensSidebar, "color");
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
});
@ -1681,10 +1693,10 @@ test.describe("Tokens - creation", () => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
await unfoldTokenType(tokensSidebar, "color");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await colorToken.click({ button: "right" });
@ -1724,7 +1736,7 @@ test("User creates grouped color token", async ({ page }) => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
await unfoldTokenType(tokensSidebar, "color");
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
});
@ -1761,10 +1773,10 @@ test("User duplicate color token", async ({ page }) => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
await unfoldTokenType(tokensSidebar, "color");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await colorToken.click({ button: "right" });
@ -1809,7 +1821,9 @@ test.describe("Tokens tab - edition", () => {
await fontFamilyField.fill("OneWord");
// Invalidate incorrect values for font size
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" });
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font Size",
});
await fontSizeField.fill("invalid");
await expect(
tokensUpdateCreateModal.getByText(/Invalid token value:/),
@ -1824,13 +1838,21 @@ test.describe("Tokens tab - edition", () => {
await fontSizeField.fill("16");
await expect(saveButton).toBeEnabled();
const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Weight" });
const letterSpacingField =
tokensUpdateCreateModal.getByRole("textbox", { name: "Letter Spacing" });
const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Line Height" });
const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text Case" });
const textDecorationField =
tokensUpdateCreateModal.getByRole("textbox", { name: "Text Decoration" });
const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font Weight",
});
const letterSpacingField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Letter Spacing",
});
const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Line Height",
});
const textCaseField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Text Case",
});
const textDecorationField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Text Decoration",
});
// Capture all values before switching tabs
const originalValues = {
@ -1883,10 +1905,10 @@ test.describe("Tokens tab - edition", () => {
await expect(tokensSidebar).toBeVisible();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
await unfoldTokenType(tokensSidebar, "color");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
@ -1904,7 +1926,7 @@ test.describe("Tokens tab - edition", () => {
await expect(tokensUpdateCreateModal).not.toBeVisible();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
await unfoldTokenType(tokensSidebar, "color");
const colorTokenChanged = tokensSidebar.getByRole("button", {
name: "changed",
@ -1975,10 +1997,10 @@ test.describe("Tokens tab - delete", () => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
await unfoldTokenType(tokensSidebar, "color");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await colorToken.click({ button: "right" });
@ -1996,7 +2018,7 @@ test.describe("Tokens tab - delete", () => {
await expect(tokensSidebar).toBeVisible();
// Expand color tokens
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
await unfoldTokenType(tokensSidebar, "color");
// Verify that the node and child token are visible before deletion
const colorNode = tokensSidebar.getByRole("button", {
@ -2004,7 +2026,7 @@ test.describe("Tokens tab - delete", () => {
exact: true,
});
const colorNodeToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
// Select a node and right click on it to open context menu

View File

@ -207,7 +207,7 @@ const testTokenCreationFlow = async (
const selfReferenceError = "Token has self reference";
const missingReferenceError = "Missing token references";
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
const { tokensUpdateCreateModal, tokensSidebar } =
await setupEmptyTokensFileRender(page);
// Open modal
@ -313,12 +313,11 @@ const testTokenCreationFlow = async (
).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 unfoldTokenType = async (tokensTabPanel, type) => {
const kebabClaseType = type.toLocaleLowerCase().replace(/\s/g, "-");
const typeParentWrapper = tokensTabPanel.getByTestId(
`section-${kebabClaseType}`,
);
const typeSectionButton = typeParentWrapper
.getByRole("button", {
name: type,
@ -331,24 +330,6 @@ const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
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();
};
export {
@ -359,5 +340,5 @@ export {
setupTypographyTokensFile,
setupTypographyTokensFileRender,
testTokenCreationFlow,
unfoldTokenTree,
unfoldTokenType,
};

View File

@ -1,7 +1,7 @@
import { test, expect } from "@playwright/test";
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
import { setupTokensFileRender, unfoldTokenTree } from "./helpers";
import { setupTokensFileRender } from "./helpers";
test.beforeEach(async ({ page }) => {
await WasmWorkspacePage.init(page);
@ -20,10 +20,8 @@ test.describe("Tokens - node tree", () => {
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await tokensColorGroup.click();

View File

@ -11,7 +11,6 @@
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.logic.tokens :as clt]
[app.common.path-names :as cpn]
[app.common.types.shape :as cts]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
@ -62,52 +61,77 @@
(watch [_ _ _]
(rx/of (dwsh/update-shapes [id] #(merge % attrs)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Toggle tree nodes
;; TOKENS TREE - Type folders
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- remove-paths-recursively
(defn open-token-type
([types type]
(conj (or types #{}) type))
([type]
(ptk/reify ::open-token-type
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-tokens :unfolded-token-types]
#(open-token-type % type))))))
(defn close-token-type
([types type]
(disj (or types #{}) type))
([type]
(ptk/reify ::close-token-type
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-tokens :unfolded-token-types]
#(close-token-type % type))))))
(defn toggle-token-type
[type]
(ptk/reify ::toggle-token-type
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-tokens :unfolded-token-types]
(fn [types]
(if (contains? (or types #{}) type)
(close-token-type types type)
(open-token-type types type)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS TREE - Toggle tree nodes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- remove-path
[path paths]
(->> paths
(remove #(str/starts-with? % (str path)))
(remove #(= % path))
vec))
(defn add-path
[path paths]
(let [split-path (cpn/split-path path :separator ".")
partial-paths (->> split-path
(reduce
(fn [acc segment]
(let [new-acc (if (empty? acc)
segment
(str (last acc) "." segment))]
(conj acc new-acc)))
[]))]
(->> paths
(into partial-paths)
distinct
vec)))
(vec (conj paths path)))
(defn clear-tokens-paths
[]
(ptk/reify ::clear-tokens-paths
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-tokens :unfolded-token-paths] []))))
(assoc-in state [:workspace-tokens :folded-token-paths] []))))
(defn toggle-token-path
[path]
(ptk/reify ::toggle-token-path
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-tokens :unfolded-token-paths]
(update-in state [:workspace-tokens :folded-token-paths]
(fn [paths]
(let [paths (or paths [])]
(if (some #(= % path) paths)
(remove-paths-recursively path paths)
(remove-path path paths)
(add-path path paths))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS Actions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -171,14 +171,11 @@
path (:name token)
tokens-by-type (ctob/group-by-type selected-token-set-tokens)
tokens-filtered-by-type (get tokens-by-type type)
tokens-in-path-ids (filter-tokens-by-path-ids type path)
remaining-tokens? (remaining-tokens-of-type-in-set? tokens-filtered-by-type tokens-in-path-ids)]
;; Delete the token
remaining-tokens? (remaining-tokens-of-type-in-set? tokens-filtered-by-type [id])]
(st/emit! (dwtl/delete-token selected-token-set-id id))
;; Remove from unfolded tree path
(if remaining-tokens?
(st/emit! (dwtl/toggle-token-path (str (name type) "." path)))
(st/emit! (dwtl/toggle-token-path (name type)))))))
(st/emit! (dwtl/close-token-type type))))))
delete-node
(mf/with-memo [selected-token-set-tokens selected-token-set-id]
@ -193,7 +190,7 @@
;; Remove from unfolded tree path
(if remaining-tokens?
(st/emit! (dwtl/toggle-token-path (str (name type) "." path)))
(st/emit! (dwtl/toggle-token-path (name type)))))))
(st/emit! (dwtl/close-token-type type))))))
bulk-rename-tokens-in-path
;; Rename tokens in bulk affected by a node rename.

View File

@ -7,7 +7,6 @@
(ns app.main.ui.workspace.tokens.management.forms.generic-form
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.schema :as sm]
[app.common.types.tokens-lib :as ctob]
@ -186,7 +185,6 @@
(mf/deps validate-token token tokens token-type value-subfield value-type active-tab on-remap-token on-rename-token is-create)
(fn [form _event]
(let [name (get-in @form [:clean-data :name])
path (str (d/name token-type) "." name)
description (get-in @form [:clean-data :description])
value (get-in @form [:clean-data :value])
value-for-validation (get-value-for-validator active-tab value value-subfield value-type)]
@ -221,7 +219,7 @@
{:name name
:value (:value valid-token)
:description description}))
(dwtl/toggle-token-path path)
(dwtl/open-token-type (:type token))
(dwtp/propagate-workspace-tokens)
(modal/hide!)))))
;; WORKAROUND: display validation errors in the form instead of crashing

View File

@ -27,8 +27,11 @@
[okulary.core :as l]
[rumext.v2 :as mf]))
(def ref:unfolded-token-paths
(l/derived (l/key :unfolded-token-paths) refs/workspace-tokens))
(def ref:folded-token-paths
(l/derived (l/key :folded-token-paths) refs/workspace-tokens))
(def ref:unfolded-token-types
(l/derived (l/key :unfolded-token-types) refs/workspace-tokens))
(defn token-section-icon
[type]
@ -72,8 +75,10 @@
(let [{:keys [modal title]}
(get dwta/token-properties type)
unfolded-token-paths (mf/deref ref:unfolded-token-paths)
is-type-unfolded (contains? (set unfolded-token-paths) (name type))
folded-token-paths (mf/deref ref:folded-token-paths)
unfolded-token-types (mf/deref ref:unfolded-token-types)
is-type-unfolded (contains? (set unfolded-token-types) type)
editing-ref (mf/deref refs/workspace-editor-state)
edition (mf/deref refs/selected-edition)
@ -117,7 +122,7 @@
(mf/deps type expandable?)
(fn []
(when expandable?
(st/emit! (dwtl/toggle-token-path (name type))))))
(st/emit! (dwtl/toggle-token-type type)))))
on-popover-open-click
(mf/use-fn
@ -172,13 +177,12 @@
(when is-type-unfolded
[:> token-tree* {:tokens tokens
:type type
:id (dm/str "token-tree-" (name type))
:tokens-lib tokens-lib
:unfolded-token-paths unfolded-token-paths
:folded-token-paths folded-token-paths
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
:selected-token-set-id selected-token-set-id
:is-selected-inside-layout is-selected-inside-layout
:tokens-lib tokens-lib
:on-token-pill-click on-token-pill-click
:on-pill-context-menu on-pill-context-menu
:on-node-context-menu on-node-context-menu}])]))

View File

@ -21,7 +21,7 @@
[:map
[:node :any]
[:type :keyword]
[:unfolded-token-paths {:optional true} [:vector :string]]
[:folded-token-paths {:optional true} [:maybe [:vector :string]]]
[:selected-shapes :any]
[:is-selected-inside-layout {:optional true} :boolean]
[:active-theme-tokens {:optional true} :any]
@ -35,7 +35,7 @@
{::mf/schema schema:folder-node}
[{:keys [node
type
unfolded-token-paths
folded-token-paths
selected-shapes
is-selected-inside-layout
active-theme-tokens
@ -45,12 +45,11 @@
on-pill-context-menu
on-node-context-menu]}]
(let [full-path (str (name type) "." (:path node))
is-folder-expanded (contains? (set (or unfolded-token-paths [])) full-path)
is-folder-expanded (not (contains? (set (or folded-token-paths [])) full-path))
swap-folder-expanded (mf/use-fn
(mf/deps (:path node) type)
(mf/deps full-path)
(fn []
(let [path (str (name type) "." (:path node))]
(st/emit! (dwtl/toggle-token-path path)))))
(st/emit! (dwtl/toggle-token-path full-path))))
node-context-menu-prep (mf/use-fn
(mf/deps on-node-context-menu node)
@ -66,18 +65,18 @@
:on-toggle-expand swap-folder-expanded
:on-context-menu node-context-menu-prep}]
(when is-folder-expanded
(let [children-fn (:children-fn node)]
(let [children (:children node)]
[:div {:class (stl/css :folder-children-wrapper)
:id (str "folder-children-" (:path node))}
(when children-fn
(let [sorted-children (d/natural-sort-by :name (children-fn))]
(when (seq children)
(let [sorted-children (d/natural-sort-by :name children)]
(for [child sorted-children]
(if (not (:leaf child))
[:ul {:class (stl/css :node-parent)
:key (:path child)}
[:> folder-node* {:type type
:node child
:unfolded-token-paths unfolded-token-paths
:folded-token-paths folded-token-paths
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
@ -101,12 +100,12 @@
[:map
[:tokens :any]
[:type :keyword]
[:unfolded-token-paths {:optional true} [:vector :string]]
[:folded-token-paths {:optional true} [:maybe [:vector :string]]]
[: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]
[:selected-token-set-id {:optional true} :any]
[:on-token-pill-click {:optional true} fn?]
[:on-pill-context-menu {:optional true} fn?]
[:on-node-context-menu {:optional true} fn?]])
@ -115,7 +114,7 @@
{::mf/schema schema:token-tree}
[{:keys [tokens
type
unfolded-token-paths
folded-token-paths
selected-shapes
is-selected-inside-layout
active-theme-tokens
@ -153,7 +152,7 @@
:key (:path node)}
[:> folder-node* {:node node
:type type
:unfolded-token-paths unfolded-token-paths
:folded-token-paths folded-token-paths
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens