mirror of
https://github.com/penpot/penpot.git
synced 2026-08-05 20:38:38 +00:00
Revert "💄 Group tokens by name path (#7775)"
This reverts commit 0956b66281bba6303e2c0acb85bb2aa6be4d9d1a.
This commit is contained in:
parent
cab70773d2
commit
eff572d3bb
@ -132,94 +132,3 @@ Some naming conventions:
|
|||||||
(if-let [last-period (str/last-index-of s ".")]
|
(if-let [last-period (str/last-index-of s ".")]
|
||||||
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
||||||
[s ""]))
|
[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)))
|
|
||||||
|
|||||||
@ -40,7 +40,6 @@ const setupEmptyTokensFile = async (page, options = {}) => {
|
|||||||
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
||||||
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
||||||
tokenSetItems: workspacePage.tokenSetItems,
|
tokenSetItems: workspacePage.tokenSetItems,
|
||||||
tokensSidebar: workspacePage.tokensSidebar,
|
|
||||||
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
||||||
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
||||||
};
|
};
|
||||||
@ -111,12 +110,15 @@ const checkInputFieldWithError = async (
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkInputFieldWithoutError = async (inputLocator) => {
|
const checkInputFieldWithoutError = async (
|
||||||
|
tokenThemeUpdateCreateModal,
|
||||||
|
inputLocator,
|
||||||
|
) => {
|
||||||
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
||||||
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
||||||
};
|
};
|
||||||
|
|
||||||
const testTokenCreationFlow = async (
|
async function testTokenCreationFlow(
|
||||||
page,
|
page,
|
||||||
{
|
{
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
@ -130,7 +132,7 @@ const testTokenCreationFlow = async (
|
|||||||
resolvedValueText,
|
resolvedValueText,
|
||||||
secondResolvedValueText,
|
secondResolvedValueText,
|
||||||
},
|
},
|
||||||
) => {
|
) {
|
||||||
const invalidValueError = "Invalid token value";
|
const invalidValueError = "Invalid token value";
|
||||||
const emptyNameError = "Name should be at least 1 character";
|
const emptyNameError = "Name should be at least 1 character";
|
||||||
const selfReferenceError = "Token has self reference";
|
const selfReferenceError = "Token has self reference";
|
||||||
@ -240,45 +242,7 @@ const testTokenCreationFlow = async (
|
|||||||
await expect(
|
await expect(
|
||||||
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
||||||
).toBeEnabled();
|
).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.describe("Tokens: Tokens Tab", () => {
|
||||||
test("Clicking tokens tab button opens tokens sidebar tab", async ({
|
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 emptyNameError = "Name should be at least 1 character";
|
||||||
const selfReferenceError = "Token has self reference";
|
const selfReferenceError = "Token has self reference";
|
||||||
const missingReferenceError = "Missing token references";
|
const missingReferenceError = "Missing token references";
|
||||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||||
await setupEmptyTokensFile(page);
|
await setupEmptyTokensFile(page);
|
||||||
|
|
||||||
await tokensSidebar
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
const addTokenButton = tokensTabPanel.getByRole("button", {
|
||||||
.click();
|
name: `Add Token: Color`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await addTokenButton.click();
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
// Placeholder checks
|
// Placeholder checks
|
||||||
@ -504,34 +471,38 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(submitButton).toBeEnabled();
|
await expect(submitButton).toBeEnabled();
|
||||||
await submitButton.click();
|
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
|
// Create token referencing the previous one with keyboard
|
||||||
|
|
||||||
await tokensSidebar
|
await tokensTabPanel
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
.click();
|
.click();
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
await nameField.click();
|
await nameField.click();
|
||||||
await nameField.fill("secondary");
|
await nameField.fill("color.secondary");
|
||||||
await nameField.press("Tab");
|
await nameField.press("Tab");
|
||||||
|
|
||||||
await valueField.click();
|
await valueField.click();
|
||||||
await valueField.fill("{color.primary}");
|
await valueField.fill("{color.primary}");
|
||||||
|
|
||||||
await expect(submitButton).toBeEnabled();
|
await expect(submitButton).toBeEnabled();
|
||||||
await submitButton.press("Enter");
|
await nameField.press("Enter");
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
tokensSidebar.getByRole("button", {
|
tokensTabPanel.getByRole("button", {
|
||||||
name: "secondary",
|
name: "color.secondary",
|
||||||
}),
|
}),
|
||||||
).toBeEnabled();
|
).toBeEnabled();
|
||||||
|
|
||||||
// Tokens tab panel should have two tokens with the color red / #ff0000
|
// Tokens tab panel should have two tokens with the color red / #ff0000
|
||||||
await expect(
|
await expect(
|
||||||
tokensSidebar.getByRole("button", { name: "#ff0000" }),
|
tokensTabPanel.getByRole("button", { name: "#ff0000" }),
|
||||||
).toHaveCount(2);
|
).toHaveCount(2);
|
||||||
|
|
||||||
// Global set has been auto created and is active
|
// Global set has been auto created and is active
|
||||||
@ -547,7 +518,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
).toHaveAttribute("aria-checked", "true");
|
).toHaveAttribute("aria-checked", "true");
|
||||||
|
|
||||||
// Check color picker
|
// Check color picker
|
||||||
await tokensSidebar
|
await tokensTabPanel
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
.click();
|
.click();
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
@ -1108,7 +1079,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
const emptyNameError = "Name should be at least 1 character";
|
const emptyNameError = "Name should be at least 1 character";
|
||||||
|
|
||||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||||
await setupEmptyTokensFile(page, { flags: ["enable-token-shadow"] });
|
await setupEmptyTokensFile(page, {flags: ["enable-token-shadow"]});
|
||||||
|
|
||||||
// Open modal
|
// Open modal
|
||||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
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 ({
|
test("User edits token and auto created set show up in the sidebar", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } =
|
const {
|
||||||
await setupTokensFile(page);
|
workspacePage,
|
||||||
|
tokensUpdateCreateModal,
|
||||||
|
tokenThemesSetsSidebar,
|
||||||
|
tokensSidebar,
|
||||||
|
tokenContextMenuForToken,
|
||||||
|
} = await setupTokensFile(page);
|
||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
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", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
await expect(colorToken).toBeVisible();
|
await expect(colorToken).toBeVisible();
|
||||||
await colorToken.click({ button: "right" });
|
await colorToken.click({ button: "right" });
|
||||||
@ -1561,10 +1541,8 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
|
|
||||||
|
|
||||||
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
||||||
name: "changed",
|
name: "colors.blue.100.changed",
|
||||||
});
|
});
|
||||||
await expect(colorTokenChanged).toBeVisible();
|
await expect(colorTokenChanged).toBeVisible();
|
||||||
});
|
});
|
||||||
@ -1655,10 +1633,11 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("User creates grouped color token", async ({ page }) => {
|
test("User creates grouped color token", async ({ page }) => {
|
||||||
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
|
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||||
await setupEmptyTokensFile(page);
|
await setupEmptyTokensFile(page);
|
||||||
|
|
||||||
await tokensSidebar
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
|
await tokensTabPanel
|
||||||
.getByRole("button", { name: "Add Token: Color" })
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
@ -1670,7 +1649,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
|
||||||
await nameField.click();
|
await nameField.click();
|
||||||
await nameField.fill("dark.primary");
|
await nameField.fill("color.dark.primary");
|
||||||
|
|
||||||
await valueField.click();
|
await valueField.click();
|
||||||
await valueField.fill("red");
|
await valueField.fill("red");
|
||||||
@ -1681,9 +1660,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(submitButton).toBeEnabled();
|
await expect(submitButton).toBeEnabled();
|
||||||
await submitButton.click();
|
await submitButton.click();
|
||||||
|
|
||||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
|
||||||
|
|
||||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("User cant create regular token with value missing", async ({
|
test("User cant create regular token with value missing", async ({
|
||||||
@ -1699,6 +1676,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
name: "Save",
|
name: "Save",
|
||||||
});
|
});
|
||||||
@ -1708,7 +1686,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
// Fill in name but leave value empty
|
// Fill in name but leave value empty
|
||||||
await nameField.click();
|
await nameField.click();
|
||||||
await nameField.fill("primary");
|
await nameField.fill("color.primary");
|
||||||
|
|
||||||
// Submit button should remain disabled when value is empty
|
// Submit button should remain disabled when value is empty
|
||||||
await expect(submitButton).toBeDisabled();
|
await expect(submitButton).toBeDisabled();
|
||||||
@ -1726,6 +1704,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
|
||||||
await valueField.click();
|
await valueField.click();
|
||||||
@ -1775,10 +1754,15 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
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", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
|
|
||||||
await colorToken.click({ button: "right" });
|
await colorToken.click({ button: "right" });
|
||||||
@ -1798,10 +1782,15 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
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", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
await expect(colorToken).toBeVisible();
|
await expect(colorToken).toBeVisible();
|
||||||
await colorToken.click({ button: "right" });
|
await colorToken.click({ button: "right" });
|
||||||
@ -1814,7 +1803,8 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("User fold/unfold color tokens", async ({ page }) => {
|
test("User fold/unfold color tokens", async ({ page }) => {
|
||||||
const { tokensSidebar } = await setupTokensFile(page);
|
const { tokensSidebar, tokenContextMenuForToken } =
|
||||||
|
await setupTokensFile(page);
|
||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
await expect(tokensSidebar).toBeVisible();
|
||||||
|
|
||||||
@ -1824,10 +1814,8 @@ test.describe("Tokens: Tokens Tab", () => {
|
|||||||
await expect(tokensColorGroup).toBeVisible();
|
await expect(tokensColorGroup).toBeVisible();
|
||||||
await tokensColorGroup.click();
|
await tokensColorGroup.click();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
|
||||||
|
|
||||||
const colorToken = tokensSidebar.getByRole("button", {
|
const colorToken = tokensSidebar.getByRole("button", {
|
||||||
name: "100",
|
name: "colors.blue.100",
|
||||||
});
|
});
|
||||||
await expect(colorToken).toBeVisible();
|
await expect(colorToken).toBeVisible();
|
||||||
await tokensColorGroup.click();
|
await tokensColorGroup.click();
|
||||||
@ -2230,10 +2218,13 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||||
await tokensTabButton.click();
|
await tokensTabButton.click();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "color", "colors.black");
|
await tokensSidebar
|
||||||
|
.getByRole("button")
|
||||||
|
.filter({ hasText: "Color" })
|
||||||
|
.click();
|
||||||
|
|
||||||
await tokensSidebar
|
await tokensSidebar
|
||||||
.getByRole("button", { name: "black" })
|
.getByRole("button", { name: "colors.black" })
|
||||||
.click({ button: "right" });
|
.click({ button: "right" });
|
||||||
await tokenContextMenuForToken.getByText("Fill").click();
|
await tokenContextMenuForToken.getByText("Fill").click();
|
||||||
|
|
||||||
@ -2471,7 +2462,7 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
|
|
||||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
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
|
// User adds first shadow with a color from the color ramp
|
||||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||||
@ -2718,11 +2709,9 @@ test.describe("Tokens: Apply token", () => {
|
|||||||
await submitButton.click();
|
await submitButton.click();
|
||||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||||
|
|
||||||
unfoldTokenTree(tokensSidebar, "shadow", "primary");
|
|
||||||
|
|
||||||
// Verify token appears in sidebar
|
// Verify token appears in sidebar
|
||||||
const shadowToken = tokensSidebar.getByRole("button", {
|
const shadowToken = tokensSidebar.getByRole("button", {
|
||||||
name: "primary",
|
name: "shadow.primary",
|
||||||
});
|
});
|
||||||
await expect(shadowToken).toBeEnabled();
|
await expect(shadowToken).toBeEnabled();
|
||||||
|
|
||||||
|
|||||||
@ -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]]))
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -44,39 +44,6 @@
|
|||||||
[(seq (array/sort! empty))
|
[(seq (array/sort! empty))
|
||||||
(seq (array/sort! filled))]))))
|
(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/defc tokens-section*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [tokens-lib active-tokens resolved-active-tokens]}]
|
[{:keys [tokens-lib active-tokens resolved-active-tokens]}]
|
||||||
@ -98,7 +65,9 @@
|
|||||||
selected-token-set-id
|
selected-token-set-id
|
||||||
(mf/deref refs/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
|
;; If we have not selected any set explicitly we just
|
||||||
;; select the first one from the list of sets
|
;; select the first one from the list of sets
|
||||||
@ -123,9 +92,15 @@
|
|||||||
tokens)]
|
tokens)]
|
||||||
(ctob/group-by-type 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]
|
[empty-group filled-group]
|
||||||
(mf/with-memo [tokens-by-type]
|
(mf/with-memo [tokens-by-type]
|
||||||
@ -143,27 +118,34 @@
|
|||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& token-context-menu]
|
[:& token-context-menu]
|
||||||
|
[:div {:class (stl/css :sets-header-container)}
|
||||||
[:& selected-set-info* {:tokens-lib tokens-lib
|
[:> text* {:as "span" :typography "headline-small" :class (stl/css :sets-header)} (tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
|
||||||
:selected-token-set-id selected-token-set-id}]
|
[: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]
|
(for [type filled-group]
|
||||||
(let [tokens (get tokens-by-type type)]
|
(let [tokens (get tokens-by-type type)]
|
||||||
[:> token-group* {:key (name type)
|
[:> token-group* {:key (name type)
|
||||||
:tokens tokens
|
:is-open (get open-status type false)
|
||||||
:is-expanded (get open-status type false)
|
|
||||||
:type type
|
:type type
|
||||||
:selected-ids selected
|
:selected-ids selected
|
||||||
:selected-shapes selected-shapes
|
:selected-shapes selected-shapes
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:is-selected-inside-layout is-selected-inside-layout
|
||||||
:active-theme-tokens resolved-active-tokens
|
:active-theme-tokens resolved-active-tokens
|
||||||
:tokens-lib tokens-lib
|
:tokens tokens}]))
|
||||||
:selected-token-set-id selected-token-set-id}]))
|
|
||||||
|
|
||||||
(for [type empty-group]
|
(for [type empty-group]
|
||||||
[:> token-group* {:key (name type)
|
[:> token-group* {:key (name type)
|
||||||
:tokens []
|
|
||||||
:type type
|
:type type
|
||||||
:selected-shapes selected-shapes
|
:selected-shapes selected-shapes
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:is-selected-inside-layout :is-selected-inside-layout
|
||||||
:active-theme-tokens resolved-active-tokens}])]))
|
:active-theme-tokens resolved-active-tokens
|
||||||
|
:tokens []}])]))
|
||||||
|
|||||||
@ -8,9 +8,6 @@
|
|||||||
(ns app.main.ui.workspace.tokens.management.group
|
(ns app.main.ui.workspace.tokens.management.group
|
||||||
(:require-macros [app.main.style :as stl])
|
(:require-macros [app.main.style :as stl])
|
||||||
(:require
|
(: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.modal :as modal]
|
||||||
[app.main.data.workspace.tokens.application :as dwta]
|
[app.main.data.workspace.tokens.application :as dwta]
|
||||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||||
@ -19,70 +16,51 @@
|
|||||||
[app.main.ui.context :as ctx]
|
[app.main.ui.context :as ctx]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||||
[app.main.ui.ds.layers.layer-button :refer [layer-button*]]
|
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||||
[app.main.ui.workspace.tokens.management.token-tree :refer [token-tree*]]
|
[app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
|
||||||
(defn token-section-icon
|
(defn token-section-icon
|
||||||
[type]
|
[type]
|
||||||
(case type
|
(case type
|
||||||
:border-radius i/corner-radius
|
:border-radius "corner-radius"
|
||||||
:color i/drop
|
:color "drop"
|
||||||
:boolean i/boolean-difference
|
:boolean "boolean-difference"
|
||||||
:font-family i/text-font-family
|
:font-family "text-font-family"
|
||||||
:font-size i/text-font-size
|
:font-size "text-font-size"
|
||||||
:letter-spacing i/text-letterspacing
|
:letter-spacing "text-letterspacing"
|
||||||
:text-case i/text-mixed
|
:text-case "text-mixed"
|
||||||
:text-decoration i/text-underlined
|
:text-decoration "text-underlined"
|
||||||
:font-weight i/text-font-weight
|
:font-weight "text-font-weight"
|
||||||
:typography i/text-typography
|
:typography "text-typography"
|
||||||
:opacity i/percentage
|
:opacity "percentage"
|
||||||
:number i/number
|
:number "number"
|
||||||
:rotation i/rotation
|
:rotation "rotation"
|
||||||
:spacing i/padding-extended
|
:spacing "padding-extended"
|
||||||
:string i/text-mixed
|
:string "text-mixed"
|
||||||
:stroke-width i/stroke-size
|
:stroke-width "stroke-size"
|
||||||
:dimensions i/expand
|
:dimensions "expand"
|
||||||
:sizing i/expand
|
:sizing "expand"
|
||||||
:shadow i/drop-shadow
|
:shadow "drop-shadow"
|
||||||
"add"))
|
"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/defc token-group*
|
||||||
{::mf/schema schema:token-group}
|
{::mf/private true}
|
||||||
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib is-expanded selected-ids]}]
|
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens is-open selected-ids]}]
|
||||||
(let [{:keys [modal title]}
|
(let [{:keys [modal title]}
|
||||||
(get dwta/token-properties type)
|
(get dwta/token-properties type)
|
||||||
editing-ref (mf/deref refs/workspace-editor-state)
|
editing-ref (mf/deref refs/workspace-editor-state)
|
||||||
not-editing? (empty? editing-ref)
|
not-editing? (empty? editing-ref)
|
||||||
|
|
||||||
is-expanded (d/nilv is-expanded false)
|
|
||||||
|
|
||||||
can-edit?
|
can-edit?
|
||||||
(mf/use-ctx ctx/can-edit?)
|
(mf/use-ctx ctx/can-edit?)
|
||||||
|
|
||||||
is-selected-inside-layout (d/nilv is-selected-inside-layout false)
|
|
||||||
|
|
||||||
tokens
|
tokens
|
||||||
(mf/with-memo [tokens]
|
(mf/with-memo [tokens]
|
||||||
(vec (sort-by :name tokens)))
|
(vec (sort-by :name tokens)))
|
||||||
|
|
||||||
expandable? (d/nilv (seq tokens) false)
|
|
||||||
|
|
||||||
on-context-menu
|
on-context-menu
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event token]
|
(fn [event token]
|
||||||
@ -95,8 +73,8 @@
|
|||||||
|
|
||||||
on-toggle-open-click
|
on-toggle-open-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps is-expanded type)
|
(mf/deps is-open type)
|
||||||
#(st/emit! (dwtl/set-token-type-section-open type (not is-expanded))))
|
#(st/emit! (dwtl/set-token-type-section-open type (not is-open))))
|
||||||
|
|
||||||
on-popover-open-click
|
on-popover-open-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -118,36 +96,33 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps not-editing? selected-ids)
|
(mf/deps not-editing? selected-ids)
|
||||||
(fn [event token]
|
(fn [event token]
|
||||||
(let [token (ctob/get-token tokens-lib selected-token-set-id (:id token))]
|
(dom/stop-propagation event)
|
||||||
(dom/stop-propagation event)
|
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
|
||||||
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
|
(st/emit! (dwta/toggle-token {:token token
|
||||||
(st/emit! (dwta/toggle-token {:token token
|
:shape-ids selected-ids})))))]
|
||||||
:shape-ids selected-ids}))))))]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :token-section-wrapper)
|
[:div {:on-click on-toggle-open-click :class (stl/css :token-section-wrapper)}
|
||||||
:data-testid (dm/str "section-" (name type))}
|
[:> cmm/asset-section* {:icon (token-section-icon type)
|
||||||
[:> layer-button* {:label title
|
:title title
|
||||||
:expanded is-expanded
|
:section :tokens
|
||||||
:description (when expandable? (dm/str (count tokens)))
|
:assets-count (count tokens)
|
||||||
:is-expandable expandable?
|
:is-open is-open}
|
||||||
:aria-expanded is-expanded
|
[:> cmm/asset-section-block* {:role :title-button}
|
||||||
:aria-controls (dm/str "token-tree-" (name type))
|
(when can-edit?
|
||||||
:on-toggle-expand on-toggle-open-click
|
[:> icon-button* {:on-click on-popover-open-click
|
||||||
:icon (token-section-icon type)}
|
:variant "ghost"
|
||||||
(when can-edit?
|
:icon i/add
|
||||||
[:> icon-button* {:id (str "add-token-button-" title)
|
:id (str "add-token-button-" title)
|
||||||
:icon "add"
|
:aria-label (tr "workspace.tokens.add-token" title)}])]
|
||||||
:aria-label (tr "workspace.tokens.add-token" title)
|
(when is-open
|
||||||
:variant "ghost"
|
[:> cmm/asset-section-block* {:role :content}
|
||||||
:on-click on-popover-open-click
|
[:div {:class (stl/css :token-pills-wrapper)}
|
||||||
:class (stl/css :token-section-icon)}])]
|
(for [token tokens]
|
||||||
(when is-expanded
|
[:> token-pill*
|
||||||
[:> token-tree* {:tokens tokens
|
{:key (:name token)
|
||||||
:id (dm/str "token-tree-" (name type))
|
:token token
|
||||||
:tokens-lib tokens-lib
|
:selected-shapes selected-shapes
|
||||||
:selected-shapes selected-shapes
|
:is-selected-inside-layout is-selected-inside-layout
|
||||||
:active-theme-tokens active-theme-tokens
|
:active-theme-tokens active-theme-tokens
|
||||||
:selected-token-set-id selected-token-set-id
|
:on-click on-token-pill-click
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:on-context-menu on-context-menu}])]])]]))
|
||||||
:on-token-pill-click on-token-pill-click
|
|
||||||
:on-context-menu on-context-menu}])]))
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -307,9 +307,10 @@
|
|||||||
:class (stl/css :token-pill-icon)}])
|
:class (stl/css :token-pill-icon)}])
|
||||||
|
|
||||||
(if contains-path?
|
(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)
|
[:span {:class (stl/css :divided-name-wrapper)
|
||||||
:aria-label name}
|
: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 :last-name-wrapper)} last-part]])
|
||||||
[:span {:class (stl/css :name-wrapper)
|
[:span {:class (stl/css :name-wrapper)
|
||||||
:aria-label name}
|
:aria-label name}
|
||||||
|
|||||||
@ -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}])])]))
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user