mirror of
https://github.com/penpot/penpot.git
synced 2026-01-15 18:00:21 -05:00
Compare commits
3 Commits
nitrate-mo
...
eva-fix-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b47a8c858e | ||
|
|
71818c4b2b | ||
|
|
df1c8b6388 |
@@ -474,8 +474,8 @@
|
||||
:height #{:sizing :dimensions}
|
||||
:max-width #{:sizing :dimensions}
|
||||
:max-height #{:sizing :dimensions}
|
||||
:x #{:spacing :dimensions}
|
||||
:y #{:spacing :dimensions}
|
||||
:x #{:dimensions}
|
||||
:y #{:dimensions}
|
||||
:rotation #{:number :rotation}
|
||||
:border-radius #{:border-radius :dimensions}
|
||||
:row-gap #{:spacing :dimensions}
|
||||
@@ -487,6 +487,7 @@
|
||||
:vertical-margin #{:spacing :dimensions}
|
||||
:sided-margins #{:spacing :dimensions}
|
||||
:line-height #{:line-height :number}
|
||||
:opacity #{:opacity}
|
||||
:font-size #{:font-size}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:fill #{:color}
|
||||
|
||||
@@ -101,6 +101,72 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(brTokenPillXL).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies opacity token to a shape from sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
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 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 expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity.high" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply opacity token from token panels
|
||||
await tokensSidebar.getByRole("button", { name: "opacity.high" }).click();
|
||||
|
||||
// Check if opacity sections is visible on right sidebar
|
||||
const layerMenuSection = page.getByRole("region", {
|
||||
name: "layer-menu-section",
|
||||
});
|
||||
await expect(layerMenuSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const opacityHighPill = layerMenuSection.getByRole("button", {
|
||||
name: "opacity.high",
|
||||
});
|
||||
await expect(opacityHighPill).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = layerMenuSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.click();
|
||||
|
||||
// Open dropdown from input
|
||||
const dropdownBtn = layerMenuSection.getByLabel("Open token list");
|
||||
await expect(dropdownBtn).toBeVisible();
|
||||
await dropdownBtn.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const opacityLowOption = layerMenuSection.getByRole("option", {
|
||||
name: "opacity.low",
|
||||
});
|
||||
await expect(opacityLowOption).toBeVisible();
|
||||
await opacityLowOption.click();
|
||||
|
||||
await expect(opacityHighPill).not.toBeVisible();
|
||||
const opacityLowPill = layerMenuSection.getByRole("button", {
|
||||
name: "opacity.low",
|
||||
});
|
||||
await expect(opacityLowPill).toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies typography token to a text shape", async ({ page }) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTypographyTokensFile(page);
|
||||
@@ -129,189 +195,6 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(fontSizeInput).toHaveValue("100");
|
||||
});
|
||||
|
||||
test("User edits typography token and all fields are valid", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button")
|
||||
.filter({ hasText: "Typography" })
|
||||
.click();
|
||||
|
||||
// Open edit modal for "Full" typography token
|
||||
const token = tokensSidebar.getByRole("button", { name: "Full" });
|
||||
await token.click({ button: "right" });
|
||||
await page.getByText("Edit token").click();
|
||||
|
||||
// Modal opens
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const saveButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: /save/i,
|
||||
});
|
||||
|
||||
// Fill font-family to verify to verify that input value doesn't get split into list of characters
|
||||
const fontFamilyField = tokensUpdateCreateModal
|
||||
.getByLabel("Font family")
|
||||
.first();
|
||||
await fontFamilyField.fill("OneWord");
|
||||
|
||||
// Invalidate incorrect values for font size
|
||||
const fontSizeField = tokensUpdateCreateModal.getByLabel(/Font Size/i);
|
||||
await fontSizeField.fill("invalid");
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(/Invalid token value:/),
|
||||
).toBeVisible();
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
// Show error with line-height depending on invalid font-size
|
||||
await fontSizeField.fill("");
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
// Fill in values for all fields and verify they persist when switching tabs
|
||||
await fontSizeField.fill("16");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const letterSpacingField =
|
||||
tokensUpdateCreateModal.getByLabel(/Letter Spacing/i);
|
||||
const lineHeightField = tokensUpdateCreateModal.getByLabel(/Line Height/i);
|
||||
const textCaseField = tokensUpdateCreateModal.getByLabel(/Text Case/i);
|
||||
const textDecorationField =
|
||||
tokensUpdateCreateModal.getByLabel(/Text Decoration/i);
|
||||
|
||||
// Capture all values before switching tabs
|
||||
const originalValues = {
|
||||
fontSize: await fontSizeField.inputValue(),
|
||||
fontFamily: await fontFamilyField.inputValue(),
|
||||
fontWeight: await fontWeightField.inputValue(),
|
||||
letterSpacing: await letterSpacingField.inputValue(),
|
||||
lineHeight: await lineHeightField.inputValue(),
|
||||
textCase: await textCaseField.inputValue(),
|
||||
textDecoration: await textDecorationField.inputValue(),
|
||||
};
|
||||
|
||||
// Switch to reference tab and back to composite tab
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
|
||||
// Empty reference tab should be disabled
|
||||
await expect(saveButton).toBeDisabled();
|
||||
|
||||
const compositeTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("composite-opt");
|
||||
await compositeTabButton.click();
|
||||
|
||||
// Filled composite tab should be enabled
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
// Verify all values are preserved after switching tabs
|
||||
await expect(fontSizeField).toHaveValue(originalValues.fontSize);
|
||||
await expect(fontFamilyField).toHaveValue(originalValues.fontFamily);
|
||||
await expect(fontWeightField).toHaveValue(originalValues.fontWeight);
|
||||
await expect(letterSpacingField).toHaveValue(originalValues.letterSpacing);
|
||||
await expect(lineHeightField).toHaveValue(originalValues.lineHeight);
|
||||
await expect(textCaseField).toHaveValue(originalValues.textCase);
|
||||
await expect(textDecorationField).toHaveValue(
|
||||
originalValues.textDecoration,
|
||||
);
|
||||
|
||||
await saveButton.click();
|
||||
|
||||
// Modal should close, token should be visible (with new name) in sidebar
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User cant submit empty typography token or reference", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Font Size");
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
await valueField.fill("");
|
||||
|
||||
// Submit button should be disabled when field is empty
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Switch to reference tab, should not be submittable either
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User adds typography token with reference", async ({ page }) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const newTokenTitle = "NewReference";
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Use a reference",
|
||||
});
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
const resolvedValue =
|
||||
await tokensUpdateCreateModal.getByText("Resolved value:");
|
||||
await expect(resolvedValue).toBeVisible();
|
||||
await expect(resolvedValue).toContainText("Font Family: 42dot Sans");
|
||||
await expect(resolvedValue).toContainText("Font Size: 100");
|
||||
await expect(resolvedValue).toContainText("Font Weight: 300");
|
||||
await expect(resolvedValue).toContainText("Letter Spacing: 2");
|
||||
await expect(resolvedValue).toContainText("Text Case: uppercase");
|
||||
await expect(resolvedValue).toContainText("Text Decoration: underline");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
const newToken = tokensSidebar.getByRole("button", {
|
||||
name: newTokenTitle,
|
||||
});
|
||||
|
||||
await expect(newToken).toBeVisible();
|
||||
});
|
||||
|
||||
test("User adds shadow token with multiple shadows and applies it to shape", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -601,4 +484,164 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(shadowSection).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
||||
test("User applies dimension token to a shape on width and height", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
// Unfolds dimensions on token panel
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar.getByRole("button", { name: "dimension.sm" }).click();
|
||||
|
||||
// Check if measures sections is visible on right sidebar
|
||||
const measuresSection = page.getByRole("region", {
|
||||
name: "shape-measures-section",
|
||||
});
|
||||
await expect(measuresSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const dimensionSMTokenPill = measuresSection.getByRole("button", {
|
||||
name: "dimension.sm",
|
||||
});
|
||||
await expect(dimensionSMTokenPill).toHaveCount(2);
|
||||
await dimensionSMTokenPill.nth(1).click();
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
await expect(dimensionSMTokenPill).toHaveCount(1);
|
||||
const dimensionXLTokenPill = measuresSection.getByRole("button", {
|
||||
name: "dimension.xl",
|
||||
});
|
||||
await expect(dimensionXLTokenPill).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = measuresSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.nth(1).click();
|
||||
await expect(dimensionXLTokenPill).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies dimension token to a shape on x position", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
// Unfolds dimensions on token panel
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "dimension.sm" })
|
||||
.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("AxisX").click();
|
||||
|
||||
// Check if measures sections is visible on right sidebar
|
||||
const measuresSection = page.getByRole("region", {
|
||||
name: "shape-measures-section",
|
||||
});
|
||||
await expect(measuresSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const dimensionSMTokenPill = measuresSection.getByRole("button", {
|
||||
name: "dimension.sm",
|
||||
});
|
||||
await expect(dimensionSMTokenPill).toBeVisible();
|
||||
await dimensionSMTokenPill.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
await expect(dimensionSMTokenPill).not.toBeVisible();
|
||||
const dimensionXLTokenPill = measuresSection.getByRole("button", {
|
||||
name: "dimension.xl",
|
||||
});
|
||||
await expect(dimensionXLTokenPill).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = measuresSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.nth(0).click();
|
||||
await expect(dimensionXLTokenPill).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies dimension token to a shape on y position", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
// Unfolds dimensions on token panel
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "dimension.sm" })
|
||||
.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Y").click();
|
||||
|
||||
// Check if measures sections is visible on right sidebar
|
||||
const measuresSection = page.getByRole("region", {
|
||||
name: "shape-measures-section",
|
||||
});
|
||||
await expect(measuresSection).toBeVisible();
|
||||
|
||||
// Check if token pill is visible on design tab on right sidebar
|
||||
const dimensionSMTokenPill = measuresSection.getByRole("button", {
|
||||
name: "dimension.sm",
|
||||
});
|
||||
await expect(dimensionSMTokenPill).toBeVisible();
|
||||
await dimensionSMTokenPill.click();
|
||||
|
||||
// Change token from dropdown
|
||||
const dimensionTokenOptionXl = measuresSection.getByLabel("dimension.xl");
|
||||
await expect(dimensionTokenOptionXl).toBeVisible();
|
||||
await dimensionTokenOptionXl.click();
|
||||
|
||||
await expect(dimensionSMTokenPill).not.toBeVisible();
|
||||
const dimensionXLTokenPill = measuresSection.getByRole("button", {
|
||||
name: "dimension.xl",
|
||||
});
|
||||
await expect(dimensionXLTokenPill).toBeVisible();
|
||||
|
||||
// Detach token from design tab on right sidebar
|
||||
const detachButton = measuresSection.getByRole("button", {
|
||||
name: "Detach token",
|
||||
});
|
||||
await detachButton.nth(0).click();
|
||||
await expect(dimensionXLTokenPill).not.toBeVisible();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1008,6 +1008,41 @@ test.describe("Tokens - CRUD", () => {
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant submit empty typography token or reference", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Font Size");
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
await valueField.fill("");
|
||||
|
||||
// Submit button should be disabled when field is empty
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Switch to reference tab, should not be submittable either
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceTabButton.click();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test("User creates typography token", async ({ page }) => {
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
@@ -1256,6 +1291,58 @@ test.describe("Tokens - CRUD", () => {
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User adds typography token with reference", async ({ page }) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupTypographyTokensFile(page);
|
||||
|
||||
const newTokenTitle = "NewReference";
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Use a reference",
|
||||
});
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Reference",
|
||||
});
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
|
||||
const resolvedValue =
|
||||
await tokensUpdateCreateModal.getByText("Resolved value:");
|
||||
await expect(resolvedValue).toBeVisible();
|
||||
await expect(resolvedValue).toContainText("Font Family: 42dot Sans");
|
||||
await expect(resolvedValue).toContainText("Font Size: 100");
|
||||
await expect(resolvedValue).toContainText("Font Weight: 300");
|
||||
await expect(resolvedValue).toContainText("Letter Spacing: 2");
|
||||
await expect(resolvedValue).toContainText("Text Case: uppercase");
|
||||
await expect(resolvedValue).toContainText("Text Decoration: underline");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
const newToken = tokensSidebar.getByRole("button", {
|
||||
name: newTokenTitle,
|
||||
});
|
||||
|
||||
await expect(newToken).toBeVisible();
|
||||
});
|
||||
|
||||
test("User edits typography token and all fields are valid", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -191,16 +191,6 @@
|
||||
(when (:fill attributes) (update-fill value shape-ids attributes page-id))
|
||||
(when (:stroke-color attributes) (update-stroke-color value shape-ids attributes page-id)))))))
|
||||
|
||||
(defn update-shape-dimensions
|
||||
([value shape-ids attributes] (update-shape-dimensions value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(ptk/reify ::update-shape-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when (number? value)
|
||||
(rx/of
|
||||
(when (:width attributes) (dwtr/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id}))
|
||||
(when (:height attributes) (dwtr/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id}))))))))
|
||||
|
||||
(defn- attributes->layout-gap [attributes value]
|
||||
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
|
||||
@@ -248,21 +238,6 @@
|
||||
{:ignore-touched true
|
||||
:page-id page-id}))))))))
|
||||
|
||||
(defn update-layout-spacing
|
||||
([value shape-ids attributes] (update-layout-spacing value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(ptk/reify ::update-layout-spacing
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when (number? value)
|
||||
(let [ids-with-layout (shape-ids-with-layout state (or page-id (:current-page-id state)) shape-ids)
|
||||
layout-attributes (attributes->layout-gap attributes value)]
|
||||
(rx/of
|
||||
(dwsl/update-layout ids-with-layout
|
||||
layout-attributes
|
||||
{:ignore-touched true
|
||||
:page-id page-id}))))))))
|
||||
|
||||
(defn update-shape-position
|
||||
([value shape-ids attributes] (update-shape-position value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
@@ -276,6 +251,20 @@
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))))))
|
||||
|
||||
(defn update-layout-gap
|
||||
[value shape-ids attributes page-id]
|
||||
(ptk/reify ::update-layout-gao
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when (number? value)
|
||||
(let [ids-with-layout (shape-ids-with-layout state (or page-id (:current-page-id state)) shape-ids)
|
||||
layout-attributes (attributes->layout-gap attributes value)]
|
||||
(rx/of
|
||||
(dwsl/update-layout ids-with-layout
|
||||
layout-attributes
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))))))
|
||||
|
||||
(defn update-layout-sizing-limits
|
||||
([value shape-ids attributes] (update-layout-sizing-limits value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
@@ -470,20 +459,126 @@
|
||||
value
|
||||
[shape-ids attributes page-id])))))
|
||||
|
||||
(defn update-typography-interactive
|
||||
([value shape-ids attributes] (update-typography value shape-ids attributes nil))
|
||||
(defn update-shape-dimensions
|
||||
([value shape-ids attributes] (update-shape-dimensions value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(when (map? value)
|
||||
(rx/merge
|
||||
(apply-functions-map
|
||||
{:font-size update-font-size
|
||||
:font-family update-font-family-interactive
|
||||
:font-weight update-font-weight-interactive
|
||||
:letter-spacing update-letter-spacing
|
||||
:text-case update-text-case
|
||||
:text-decoration update-text-decoration-interactive}
|
||||
value
|
||||
[shape-ids attributes page-id])))))
|
||||
(ptk/reify ::update-shape-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when (number? value)
|
||||
(rx/of
|
||||
(when (:width attributes) (dwtr/update-dimensions shape-ids :width value {:ignore-touched true :page-id page-id}))
|
||||
(when (:height attributes) (dwtr/update-dimensions shape-ids :height value {:ignore-touched true :page-id page-id}))))))))
|
||||
|
||||
(defn- attributes->actions
|
||||
[{:keys [value shape-ids attributes page-id]}]
|
||||
(cond-> []
|
||||
(some attributes #{:width :height})
|
||||
(conj #(update-shape-dimensions
|
||||
value shape-ids
|
||||
(set (filter attributes #{:width :height}))
|
||||
page-id))
|
||||
|
||||
(some attributes #{:x :y})
|
||||
(conj #(update-shape-position
|
||||
value shape-ids
|
||||
(set (filter attributes #{:x :y}))
|
||||
page-id))
|
||||
|
||||
(some attributes #{:p1 :p2 :p3 :p4})
|
||||
(conj #(update-layout-padding
|
||||
value shape-ids
|
||||
(set (filter attributes #{:p1 :p2 :p3 :p4}))
|
||||
page-id))
|
||||
|
||||
(some attributes #{:m1 :m2 :m3 :m4})
|
||||
(conj #(update-layout-item-margin
|
||||
value shape-ids
|
||||
(set (filter attributes #{:m1 :m2 :m3 :m4}))
|
||||
page-id))
|
||||
|
||||
(some attributes #{:row-gap :column-gap})
|
||||
(conj #(update-layout-gap
|
||||
value shape-ids
|
||||
(set (filter attributes #{:row-gap :column-gap}))
|
||||
page-id))
|
||||
|
||||
(some attributes #{:r1 :r2 :r3 :r4})
|
||||
(conj #(if (= attributes #{:r1 :r2 :r3 :r4})
|
||||
(update-shape-radius-all value shape-ids attributes page-id)
|
||||
(update-shape-radius-for-corners
|
||||
value shape-ids
|
||||
(set (filter attributes #{:r1 :r2 :r3 :r4}))
|
||||
page-id)))
|
||||
|
||||
(some attributes #{:strole-width})
|
||||
(conj #(update-stroke-width
|
||||
value shape-ids
|
||||
#{:strole-width}
|
||||
page-id))
|
||||
(some attributes #{:max-width :max-height})
|
||||
(conj #(update-layout-sizing-limits
|
||||
value shape-ids
|
||||
(set (filter attributes #{:max-width :max-height}))
|
||||
page-id))))
|
||||
|
||||
(defn use-dimensions-token
|
||||
([value shape-ids attributes] (use-dimensions-token value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(ptk/reify ::use-dimensions-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when (number? value)
|
||||
(let [actions (attributes->actions
|
||||
{:value value
|
||||
:shape-ids shape-ids
|
||||
:attributes attributes
|
||||
:page-id page-id
|
||||
:state state})]
|
||||
(apply rx/of (map #(%) actions))))))))
|
||||
|
||||
(defn use-spacing-token
|
||||
([value shape-ids attributes] (use-spacing-token value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(ptk/reify ::use-spacing-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [spacing-attrs
|
||||
#{:row-gap :column-gap
|
||||
:m1 :m2 :m3 :m4
|
||||
:p1 :p2 :p3 :p4}]
|
||||
(when (and (number? value)
|
||||
(set? attributes)
|
||||
(set/subset? attributes spacing-attrs))
|
||||
|
||||
(let [actions (attributes->actions
|
||||
{:value value
|
||||
:shape-ids shape-ids
|
||||
:attributes attributes
|
||||
:page-id page-id
|
||||
:state state})]
|
||||
(apply rx/of (map #(%) actions)))))))))
|
||||
|
||||
(defn use-sizing-token
|
||||
([value shape-ids attributes] (use-sizing-token value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(ptk/reify ::use-sizing-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [sizing-attrs
|
||||
#{:width :height
|
||||
:max-width :max-height}]
|
||||
(when (and (number? value)
|
||||
(set? attributes)
|
||||
(set/subset? attributes sizing-attrs))
|
||||
|
||||
(let [actions (attributes->actions
|
||||
{:value value
|
||||
:shape-ids shape-ids
|
||||
:attributes attributes
|
||||
:page-id page-id
|
||||
:state state})]
|
||||
(apply rx/of (map #(%) actions)))))))))
|
||||
|
||||
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
|
||||
|
||||
@@ -623,54 +718,19 @@
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(case (:type token)
|
||||
:spacing
|
||||
(cond
|
||||
(and (= (:type token) :spacing)
|
||||
(nil? attrs))
|
||||
(apply-spacing-token {:token token
|
||||
:attr attrs
|
||||
:shapes shapes})
|
||||
|
||||
:else
|
||||
(apply-token {:attributes (if (empty? attrs) attributes attrs)
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape}))))))))
|
||||
|
||||
(defn toggle-border-radius-token
|
||||
[{:keys [token attrs shape-ids expand-with-children]}]
|
||||
(ptk/reify ::on-toggle-border-radius-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shapes (into [] (keep (d/getf objects)) shape-ids)
|
||||
|
||||
shapes
|
||||
(if expand-with-children
|
||||
(into []
|
||||
(mapcat (fn [shape]
|
||||
(if (= (:type shape) :group)
|
||||
(keep objects (:shapes shape))
|
||||
[shape])))
|
||||
shapes)
|
||||
shapes)
|
||||
|
||||
{:keys [attributes all-attributes]}
|
||||
(get token-properties (:type token))
|
||||
|
||||
unapply-tokens?
|
||||
(cft/shapes-token-applied? token shapes (or attrs all-attributes attributes))
|
||||
|
||||
shape-ids (map :id shapes)]
|
||||
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes (or attrs all-attributes attributes)
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(apply-token {:attributes attrs
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape update-shape-radius-for-corners})))))))
|
||||
|
||||
|
||||
(defn apply-token-on-selected
|
||||
[color-operations token]
|
||||
(ptk/reify ::apply-token-on-selected
|
||||
@@ -800,7 +860,7 @@
|
||||
{:title "Sizing"
|
||||
:attributes #{:width :height}
|
||||
:all-attributes ctt/sizing-keys
|
||||
:on-update-shape update-shape-dimensions
|
||||
:on-update-shape use-sizing-token
|
||||
:modal {:key :tokens/sizing
|
||||
:fields [{:label "Sizing"
|
||||
:key :sizing}]}}
|
||||
@@ -813,7 +873,7 @@
|
||||
ctt/border-radius-keys
|
||||
ctt/axis-keys
|
||||
ctt/stroke-width-keys)
|
||||
:on-update-shape update-shape-dimensions
|
||||
:on-update-shape use-dimensions-token
|
||||
:modal {:key :tokens/dimensions
|
||||
:fields [{:label "Dimensions"
|
||||
:key :dimensions}]}}
|
||||
@@ -846,7 +906,7 @@
|
||||
{:title "Spacing"
|
||||
:attributes #{:column-gap :row-gap}
|
||||
:all-attributes ctt/spacing-keys
|
||||
:on-update-shape update-layout-spacing
|
||||
:on-update-shape use-spacing-token
|
||||
:modal {:key :tokens/spacing
|
||||
:fields [{:label "Spacing"
|
||||
:key :spacing}]}}))
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
{ctt/border-radius-keys dwta/update-shape-radius-for-corners
|
||||
ctt/color-keys dwta/update-fill-stroke
|
||||
ctt/stroke-width-keys dwta/update-stroke-width
|
||||
ctt/sizing-keys dwta/update-shape-dimensions
|
||||
ctt/sizing-keys dwta/use-dimensions-token
|
||||
ctt/opacity-keys dwta/update-opacity
|
||||
ctt/rotation-keys dwta/update-rotation
|
||||
|
||||
@@ -73,8 +73,8 @@
|
||||
#{:x :y} dwta/update-shape-position
|
||||
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
|
||||
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
|
||||
#{:column-gap :row-gap} dwta/update-layout-spacing
|
||||
#{:width :height} dwta/update-shape-dimensions
|
||||
#{:column-gap :row-gap} dwta/update-layout-gap
|
||||
#{:width :height} dwta/use-dimensions-token
|
||||
#{:layout-item-min-w :layout-item-min-h :layout-item-max-w :layout-item-max-h} dwta/update-layout-sizing-limits})
|
||||
|
||||
(def ^:private attribute-actions-map
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
is-selected-on-focus nillable
|
||||
tokens applied-token empty-to-end
|
||||
on-change on-blur on-focus on-detach
|
||||
property align ref]
|
||||
property align ref name]
|
||||
:rest props}]
|
||||
|
||||
(let [;; NOTE: we use mfu/bean here for transparently handle
|
||||
@@ -662,7 +662,10 @@
|
||||
label (get token :name)
|
||||
token-value (or (get token :resolved-value)
|
||||
(or (mf/ref-val last-value*)
|
||||
(fmt/format-number value)))]
|
||||
(fmt/format-number value)))
|
||||
token-value (if (= name :opacity)
|
||||
(* 100 token-value)
|
||||
token-value)]
|
||||
(mf/spread-props props
|
||||
{:id id
|
||||
:label label
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/typography.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.option-list {
|
||||
--options-dropdown-icon-fg-color: var(--color-foreground-secondary);
|
||||
@@ -15,32 +16,32 @@
|
||||
--options-dropdown-border-color: var(--color-background-quaternary);
|
||||
|
||||
position: absolute;
|
||||
top: $sz-36;
|
||||
width: var(--dropdown-width, 100%);
|
||||
inset-block-start: $sz-36;
|
||||
inline-size: var(--dropdown-width, 100%);
|
||||
transform: translateX(var(--dropdown-translate-distance, 0));
|
||||
background-color: var(--options-dropdown-bg-color);
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--options-dropdown-border-color);
|
||||
padding-block: var(--sp-xs);
|
||||
margin-block-end: 0;
|
||||
max-height: $sz-400;
|
||||
max-block-size: $sz-400;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: var(--z-index-dropdown);
|
||||
}
|
||||
|
||||
.left-align {
|
||||
left: var(--dropdown-offset, 0);
|
||||
inset-inline-start: var(--dropdown-offset, 0);
|
||||
}
|
||||
|
||||
.right-align {
|
||||
right: var(--dropdown-offset, 0);
|
||||
inset-inline-end: var(--dropdown-offset, 0);
|
||||
}
|
||||
|
||||
.option-separator {
|
||||
border: $b-1 solid var(--options-dropdown-border-color);
|
||||
margin-top: var(--sp-xs);
|
||||
margin-bottom: var(--sp-xs);
|
||||
margin-block-start: var(--sp-xs);
|
||||
margin-block-end: var(--sp-xs);
|
||||
}
|
||||
|
||||
.group-option,
|
||||
@@ -51,11 +52,11 @@
|
||||
gap: var(--sp-xs);
|
||||
color: var(--color-foreground-secondary);
|
||||
padding-inline: var(--sp-s);
|
||||
height: var(--sp-xxxl);
|
||||
block-size: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.option-empty {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 0 40px;
|
||||
padding: 0 px2rem(40);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
@@ -193,11 +192,10 @@
|
||||
(st/emit!
|
||||
(change-radius (fn [shape]
|
||||
(ctsr/set-radius-to-all-corners shape value))))
|
||||
(doseq [attr [:r1 :r2 :r3 :r4]]
|
||||
(st/emit!
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))))))
|
||||
(st/emit!
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{:r1 :r2 :r3 :r4}
|
||||
:shape-ids ids})))))
|
||||
|
||||
|
||||
on-single-radius-change
|
||||
@@ -206,9 +204,10 @@
|
||||
(fn [value attr]
|
||||
(if (or (string? value) (number? value))
|
||||
(st/emit! (change-one-radius #(ctsr/set-radius-to-single-corner % attr value) attr))
|
||||
(st/emit! (dwta/toggle-border-radius-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids})))))
|
||||
(st/emit! (st/emit!
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))))))
|
||||
|
||||
on-radius-r1-change #(on-single-radius-change % :r1)
|
||||
on-radius-r2-change #(on-single-radius-change % :r2)
|
||||
|
||||
@@ -9,13 +9,17 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -39,11 +43,16 @@
|
||||
(defn- check-layer-menu-props
|
||||
[old-props new-props]
|
||||
(let [old-values (unchecked-get old-props "values")
|
||||
new-values (unchecked-get new-props "values")]
|
||||
new-values (unchecked-get new-props "values")
|
||||
|
||||
old-applied-tokens (unchecked-get old-props "appliedTokens")
|
||||
new-applied-tokens (unchecked-get new-props "appliedTokens")]
|
||||
(and (identical? (unchecked-get old-props "class")
|
||||
(unchecked-get new-props "class"))
|
||||
(identical? (unchecked-get old-props "ids")
|
||||
(unchecked-get new-props "ids"))
|
||||
(identical? old-applied-tokens
|
||||
new-applied-tokens)
|
||||
(identical? (get old-values :opacity)
|
||||
(get new-values :opacity))
|
||||
(identical? (get old-values :blend-mode)
|
||||
@@ -53,12 +62,54 @@
|
||||
(identical? (get old-values :hidden)
|
||||
(get new-values :hidden)))))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
applied-token (get applied-tokens name)
|
||||
opacity-value (or (get values name) 1)
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (or (= :multiple (:applied-tokens values))
|
||||
(= :multiple opacity-value))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:name name
|
||||
:value (* 100 opacity-value)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc layer-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-layer-menu-props)]}
|
||||
[{:keys [ids values]}]
|
||||
(let [hidden? (get values :hidden)
|
||||
[{:keys [ids values applied-tokens]}]
|
||||
(let [token-numeric-inputs
|
||||
(features/use-feature "tokens/numeric-input")
|
||||
|
||||
hidden? (get values :hidden)
|
||||
blocked? (get values :blocked)
|
||||
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [token attr]
|
||||
(st/emit! (dwta/unapply-token {:token (first token)
|
||||
:attributes #{attr}
|
||||
:shape-ids ids}))))
|
||||
|
||||
current-blend-mode (or (get values :blend-mode) :normal)
|
||||
current-opacity (opacity->string (:opacity values))
|
||||
|
||||
@@ -118,6 +169,17 @@
|
||||
(let [value (/ value 100)]
|
||||
(on-change ids :opacity value))))
|
||||
|
||||
on-opacity-change
|
||||
(mf/use-fn
|
||||
(mf/deps on-change handle-opacity-change)
|
||||
(fn [value]
|
||||
(if (or (string? value) (int? value))
|
||||
(handle-opacity-change value)
|
||||
(do
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs #{:opacity}
|
||||
:shape-ids ids}))))))
|
||||
|
||||
handle-set-hidden
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
@@ -176,8 +238,9 @@
|
||||
preview-complete?))
|
||||
(swap! state* assoc :selected-blend-mode current-blend-mode)))
|
||||
|
||||
[:div {:class (stl/css-case :element-set-content true
|
||||
:hidden hidden?)}
|
||||
[:section {:class (stl/css-case :element-set-content true
|
||||
:hidden hidden?)
|
||||
:aria-label "layer-menu-section"}
|
||||
[:div {:class (stl/css :select)}
|
||||
[:& select
|
||||
{:default-value selected-blend-mode
|
||||
@@ -187,16 +250,34 @@
|
||||
:class (stl/css-case :hidden-select hidden?)
|
||||
:on-pointer-enter-option handle-blend-mode-enter
|
||||
:on-pointer-leave-option handle-blend-mode-leave}]]
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
[:span {:class (stl/css :icon)} "%"]
|
||||
[:> numeric-input*
|
||||
{:value current-opacity
|
||||
:placeholder "--"
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]]
|
||||
|
||||
|
||||
|
||||
(if token-numeric-inputs
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-opacity-change
|
||||
:on-detach on-detach-token
|
||||
:icon i/percentage
|
||||
:min 0
|
||||
:max 100
|
||||
:name :opacity
|
||||
:property (tr "workspace.options.opacity")
|
||||
:applied-tokens applied-tokens
|
||||
:align :right
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:values values}]
|
||||
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
[:span {:class (stl/css :icon)} "%"]
|
||||
[:> deprecated-input/numeric-input*
|
||||
{:value current-opacity
|
||||
:placeholder "--"
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100
|
||||
:className (stl/css :numeric-input)}]])
|
||||
|
||||
|
||||
[:div {:class (stl/css :actions)}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
@use "ds/_utils.scss" as *;
|
||||
|
||||
.element-set-content {
|
||||
@include sidebar.option-grid-structure;
|
||||
@@ -43,3 +44,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.numeric-input-wrapper {
|
||||
grid-column: span 2;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
--dropdown-offset: #{px2rem(-35)};
|
||||
}
|
||||
|
||||
@@ -369,12 +369,12 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(on-change :simple attr value event)
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))
|
||||
updated-attr (if (= :p1 attr) #{:p1 :p3} #{:p2 :p4})]
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs updated-attr
|
||||
:shape-ids ids}))
|
||||
(on-change :simple attr resolved-value event))))))
|
||||
(st/emit!
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs (if (= :p1 attr)
|
||||
#{:p1 :p3}
|
||||
#{:p2 :p4})
|
||||
:shape-ids ids}))))))
|
||||
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
@@ -483,11 +483,9 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(on-change :multiple attr value event)
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))
|
||||
(on-change :multiple attr resolved-value event))))))
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))))))
|
||||
|
||||
on-focus
|
||||
(mf/use-fn
|
||||
@@ -716,11 +714,12 @@
|
||||
(if (or (string? value) (int? value))
|
||||
(on-change (= "nowrap" wrap-type) attr value event)
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))
|
||||
(on-change (= "nowrap" wrap-type) attr resolved-value event))))))
|
||||
(st/emit!
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs (if (= "nowrap" wrap-type)
|
||||
#{:row-gap :colum-gap}
|
||||
#{attr})
|
||||
:shape-ids ids}))))))
|
||||
|
||||
on-detach-token
|
||||
(mf/use-fn
|
||||
|
||||
@@ -284,28 +284,17 @@
|
||||
(st/emit! (udw/change-orientation ids (keyword orientation)))))
|
||||
|
||||
;; SIZE AND PROPORTION LOCK
|
||||
do-size-change
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value))))
|
||||
|
||||
on-size-change
|
||||
(mf/use-fn
|
||||
(mf/deps ids shapes)
|
||||
(fn [value attr]
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(run! #(do-size-change value attr) shapes))
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))
|
||||
(run! #(do-size-change resolved-value attr) shapes))))))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(udw/update-dimensions ids attr value))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids})))))
|
||||
|
||||
on-proportion-lock-change
|
||||
(mf/use-fn
|
||||
@@ -315,11 +304,6 @@
|
||||
(run! #(st/emit! (udw/set-shape-proportion-lock % new-lock)) ids))))
|
||||
|
||||
;; POSITION
|
||||
do-position-change
|
||||
(mf/use-fn
|
||||
(fn [shape' value attr]
|
||||
(st/emit! (udw/update-position (:id shape') {attr value}))))
|
||||
|
||||
on-position-change
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
@@ -327,21 +311,11 @@
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(run! #(do-position-change %1 value attr) shapes))
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids}))
|
||||
(run! #(do-position-change %1 resolved-value attr) shapes))))))
|
||||
|
||||
;; ROTATION
|
||||
do-rotation-change
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value]
|
||||
(st/emit! (udw/increase-rotation ids value))))
|
||||
(st/emit! (udw/update-position ids {attr value})))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{attr}
|
||||
:shape-ids ids})))))
|
||||
|
||||
on-rotation-change
|
||||
(mf/use-fn
|
||||
@@ -350,14 +324,11 @@
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(run! #(do-rotation-change value) shapes))
|
||||
(do
|
||||
(let [resolved-value (:resolved-value (first value))]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{:rotation}
|
||||
:shape-ids ids}))
|
||||
(run! #(do-rotation-change resolved-value) shapes))))))
|
||||
(st/emit! (udw/increase-rotation ids value)))
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||
(dwta/toggle-token {:token (first value)
|
||||
:attrs #{:rotation}
|
||||
:shape-ids ids})))))
|
||||
|
||||
on-width-change
|
||||
(mf/use-fn (mf/deps on-size-change) #(on-size-change % :width))
|
||||
@@ -410,7 +381,8 @@
|
||||
(fn []
|
||||
(st/emit! (dwt/selected-fit-content))))]
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:section {:class (stl/css :element-set)
|
||||
:aria-label "shape-measures-section"}
|
||||
(when (and (options :presets)
|
||||
(or (nil? all-types) (= (count all-types) 1)))
|
||||
[:div {:class (stl/css :presets)}
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type shape-type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
:applied-tokens applied-tokens
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
[:div {:class (stl/css :options)}
|
||||
[:> layer-menu* {:type type
|
||||
:ids layer-ids
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:type type
|
||||
:ids measure-ids
|
||||
|
||||
@@ -382,7 +382,7 @@
|
||||
objects
|
||||
objects)))
|
||||
|
||||
[layer-ids layer-values]
|
||||
[layer-ids layer-values layer-tokens]
|
||||
(get-attrs shapes objects :layer)
|
||||
|
||||
[text-ids text-values]
|
||||
@@ -406,7 +406,7 @@
|
||||
[exports-ids exports-values]
|
||||
(get-attrs shapes objects :exports)
|
||||
|
||||
[layout-container-ids layout-container-values layout-contianer-tokens]
|
||||
[layout-container-ids layout-container-values layout-container-tokens]
|
||||
(get-attrs shapes objects :layout-container)
|
||||
|
||||
[layout-item-ids layout-item-values {}]
|
||||
@@ -442,6 +442,7 @@
|
||||
(when-not (empty? layer-ids)
|
||||
[:> layer-menu* {:type type
|
||||
:ids layer-ids
|
||||
:applied-tokens layer-tokens
|
||||
:values layer-values}])
|
||||
|
||||
(when-not (empty? measure-ids)
|
||||
@@ -459,7 +460,7 @@
|
||||
{:type type
|
||||
:ids layout-container-ids
|
||||
:values layout-container-values
|
||||
:applied-tokens layout-contianer-tokens
|
||||
:applied-tokens layout-container-tokens
|
||||
:multiple true}]
|
||||
|
||||
(when (or is-layout-child? has-flex-layout-container?)
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:applied-tokens applied-tokens
|
||||
:type type
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu* {:ids ids
|
||||
:type type
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
[:*
|
||||
[:> layer-menu* {:ids ids
|
||||
:type type
|
||||
:applied-tokens applied-tokens
|
||||
:values layer-values}]
|
||||
[:> measures-menu*
|
||||
{:ids ids
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
gap-items (all-or-separate-actions {:attribute-labels {:column-gap "Column Gap"
|
||||
:row-gap "Row Gap"}
|
||||
:hint (tr "workspace.tokens.gaps")
|
||||
:on-update-shape dwta/update-layout-spacing}
|
||||
:on-update-shape dwta/update-layout-gap}
|
||||
context-data)]
|
||||
(->> (concat
|
||||
gap-items
|
||||
@@ -239,7 +239,7 @@
|
||||
(all-or-separate-actions {:attribute-labels {:width "Width"
|
||||
:height "Height"}
|
||||
:hint (tr "workspace.tokens.size")
|
||||
:on-update-shape dwta/update-shape-dimensions}
|
||||
:on-update-shape dwta/use-dimensions-token}
|
||||
context-data)
|
||||
[:separator]
|
||||
(all-or-separate-actions {:attribute-labels {:layout-item-min-w "Min Width"
|
||||
|
||||
@@ -260,7 +260,7 @@
|
||||
events [(dwta/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:width :height}
|
||||
:token (toht/get-token file "dimensions.sm")
|
||||
:on-update-shape dwta/update-shape-dimensions})]]
|
||||
:on-update-shape dwta/use-dimensions-token})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
@@ -333,7 +333,7 @@
|
||||
events [(dwta/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:width :height}
|
||||
:token (toht/get-token file "sizing.sm")
|
||||
:on-update-shape dwta/update-shape-dimensions})]]
|
||||
:on-update-shape dwta/use-dimensions-token})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
|
||||
Reference in New Issue
Block a user