Compare commits

..

1 Commits

Author SHA1 Message Date
Luis de Dios
f223672fca ♻️ Extract and create panel title component 2026-01-15 09:39:47 +01:00
34 changed files with 369 additions and 420 deletions

View File

@@ -487,7 +487,6 @@
: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}

View File

@@ -101,70 +101,6 @@ 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);
@@ -193,6 +129,189 @@ 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,
}) => {

View File

@@ -1008,41 +1008,6 @@ 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 } =
@@ -1291,58 +1256,6 @@ 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,
}) => {

View File

@@ -17,7 +17,6 @@
--app-background: var(--color-background-primary);
--loader-background: var(--color-background-primary);
--panel-title-background-color: var(--color-background-secondary);
// BUTTONS
--button-foreground-hover: var(--color-accent-primary);

View File

@@ -38,6 +38,7 @@
[app.main.ui.ds.product.loader :refer [loader*]]
[app.main.ui.ds.product.milestone :refer [milestone*]]
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.ds.storybook :as sb]
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
[app.main.ui.ds.utilities.date :refer [date*]]
@@ -81,6 +82,7 @@
:Milestone milestone*
:MilestoneGroup milestone-group*
:Date date*
:PanelTitle panel-title*
:set-default-translations
(fn [data]

View File

@@ -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 name]
property align ref]
:rest props}]
(let [;; NOTE: we use mfu/bean here for transparently handle
@@ -662,10 +662,7 @@
label (get token :name)
token-value (or (get token :resolved-value)
(or (mf/ref-val last-value*)
(fmt/format-number value)))
token-value (if (= name :opacity)
(* 100 token-value)
token-value)]
(fmt/format-number value)))]
(mf/spread-props props
{:id id
:label label

View File

@@ -7,7 +7,6 @@
@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);
@@ -16,32 +15,32 @@
--options-dropdown-border-color: var(--color-background-quaternary);
position: absolute;
inset-block-start: $sz-36;
inline-size: var(--dropdown-width, 100%);
top: $sz-36;
width: 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-block-size: $sz-400;
max-height: $sz-400;
overflow-y: auto;
overflow-x: hidden;
z-index: var(--z-index-dropdown);
}
.left-align {
inset-inline-start: var(--dropdown-offset, 0);
left: var(--dropdown-offset, 0);
}
.right-align {
inset-inline-end: var(--dropdown-offset, 0);
right: var(--dropdown-offset, 0);
}
.option-separator {
border: $b-1 solid var(--options-dropdown-border-color);
margin-block-start: var(--sp-xs);
margin-block-end: var(--sp-xs);
margin-top: var(--sp-xs);
margin-bottom: var(--sp-xs);
}
.group-option,
@@ -52,11 +51,11 @@
gap: var(--sp-xs);
color: var(--color-foreground-secondary);
padding-inline: var(--sp-s);
block-size: var(--sp-xxxl);
height: var(--sp-xxxl);
}
.option-empty {
justify-content: center;
text-align: center;
padding: 0 px2rem(40);
padding: 0 40px;
}

View File

@@ -0,0 +1,34 @@
;; 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.product.panel-title
(:require-macros
[app.main.style :as stl])
(:require
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(def ^:private schema:panel-title
[:map
[:class {:optional true} :string]
[:text :string]
[:on-close {:optional true} fn?]])
(mf/defc panel-title*
{::mf/schema schema:panel-title}
[{:keys [class text on-close] :rest props}]
(let [props
(mf/spread-props props {:class [class (stl/css :panel-title)]})]
[:> :div props
[:span {:class (stl/css :panel-title-text)} text]
(when on-close
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.close")
:on-click on-close
:icon i/close}])]))

View File

@@ -0,0 +1,26 @@
{ /* 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 */ }
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as PanelTitle from "./panel_title.stories";
<Meta title="Product/PanelTitle" />
# PanelTitle
The `panel-title*` is used as a header for some sidebar sections.
<Canvas of={PanelTitle.Default} />
## Technical notes
The only mandatory parameter is `text`. Usually you'll want to pass a function property `on-close` that will be called when the user clicks on the close button on the right.
```clj
[:> panel-title* {:class class
:text text
:on-close on-close}]
```

View File

@@ -0,0 +1,25 @@
// 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/_sizes.scss" as *;
@use "ds/_borders.scss" as *;
@use "ds/typography.scss" as t;
.panel-title {
display: flex;
align-items: center;
justify-content: center;
block-size: $sz-32;
border-radius: $br-8;
background-color: var(--color-background-secondary);
}
.panel-title-text {
@include t.use-typography("headline-small");
flex-grow: 1;
text-align: center;
color: var(--color-foreground-primary);
}

View File

@@ -0,0 +1,21 @@
import * as React from "react";
import Components from "@target/components";
const { PanelTitle } = Components;
export default {
title: "Product/PanelTitle",
component: PanelTitle,
argTypes: {
text: {
control: { type: "text" },
},
},
args: {
text: "Lorem ipsum",
onClose: () => null,
},
render: ({ ...args }) => <PanelTitle {...args} />,
};
export const Default = {};

View File

@@ -16,9 +16,9 @@
[app.main.ui.comments :as cmt]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@@ -121,15 +121,12 @@
(st/emit! (with-meta (dcmt/open-thread thread) {::ev/origin "viewer"}))
(st/emit! (dwcm/navigate-to-comment thread)))))]
[:div {:class (stl/css-case :comments-section true
:from-viewer from-viewer)}
[:div {:class (stl/css-case :comments-section-title true
:viewer-title from-viewer)}
[:span (tr "labels.comments")]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.close")
:on-click close-section
:icon i/close}]]
[:div {:class (stl/css-case :comments-section true
:from-viewer from-viewer)}
[:> panel-title* {:class (stl/css :comments-title)
:text (tr "labels.comments")
:on-close close-section}]
[:button {:class (stl/css :mode-dropdown-wrapper)
:on-click toggle-mode-selector}

View File

@@ -18,25 +18,8 @@
padding: 0 deprecated.$s-8;
}
.comments-section-title {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
}
.viewer-title {
margin: 0;
margin-block-start: deprecated.$s-8;
.comments-title {
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
}
.mode-dropdown-wrapper {

View File

@@ -11,12 +11,11 @@
[app.common.data.macros :as dm]
[app.main.data.workspace :as dw]
[app.main.store :as st]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.main.ui.icons :as deprecated-icon]
[app.util.debug :as dbg]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc debug-panel*
@@ -35,12 +34,9 @@
(st/emit! (dw/remove-layout-flag :debug-panel))))]
[:div {:class (dm/str class " " (stl/css :debug-panel))}
[:div {:class (stl/css :panel-title)}
[:span "Debugging tools"]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.close")
:on-click handle-close
:icon i/close}]]
[:> panel-title* {:class (stl/css :debug-panel-title)
:text (tr "workspace.debug.title")
:on-close handle-close}]
[:div {:class (stl/css :debug-panel-inner)}
(for [option (sort-by d/name dbg/options)]

View File

@@ -12,21 +12,12 @@
background-color: var(--panel-background-color);
}
.panel-title {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
.debug-panel-title {
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
}
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
.debug-panel-inner {
padding: deprecated.$s-16 deprecated.$s-8;
}
.checkbox-wrapper {
@@ -39,7 +30,3 @@
@extend .checkbox-icon;
cursor: pointer;
}
.debug-panel-inner {
padding: deprecated.$s-16 deprecated.$s-8;
}

View File

@@ -13,7 +13,7 @@
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[debug :as dbg]
[rumext.v2 :as mf]))
@@ -125,11 +125,9 @@
(map (d/getf objects)))]
[:div {:class (stl/css :shape-info)}
[:div {:class (stl/css :shape-info-title)}
[:span "Debug"]
[:div {:class (stl/css :close-button)
:on-click #(dbg/disable! :shape-panel)}
deprecated-icon/close]]
[:> panel-title* {:class (stl/css :shape-info-title)
:text "Debug"
:on-close #(dbg/disable! :shape-panel)}]
(if (empty? selected)
[:div {:class (stl/css :attrs-container)} "No shapes selected"]

View File

@@ -16,34 +16,7 @@
}
.shape-info-title {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
}
.close-button {
@extend .button-tertiary;
position: absolute;
right: deprecated.$s-2;
top: deprecated.$s-2;
height: deprecated.$s-28;
width: deprecated.$s-28;
border-radius: deprecated.$br-6;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
}
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
}
.attrs-container {

View File

@@ -13,23 +13,6 @@
background-color: var(--panel-background-color);
}
.history-toolbox-title {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
min-height: deprecated.$s-32;
margin: deprecated.$s-8 deprecated.$s-8 0 deprecated.$s-8;
border-radius: deprecated.$br-8;
background-color: var(--panel-title-background-color);
span {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
}
.history-entry-empty {
display: flex;
flex-direction: column;

View File

@@ -55,6 +55,7 @@
(-> (deref tokens)
(select-keys (get tk/tokens-by-input name))
(not-empty))))
on-detach-attr
(mf/use-fn
(mf/deps on-detach name)

View File

@@ -9,17 +9,13 @@
(: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 :as deprecated-input]
[app.main.ui.components.numeric-input :refer [numeric-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]]
@@ -43,16 +39,11 @@
(defn- check-layer-menu-props
[old-props new-props]
(let [old-values (unchecked-get old-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")]
new-values (unchecked-get new-props "values")]
(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)
@@ -62,54 +53,12 @@
(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 applied-tokens]}]
(let [token-numeric-inputs
(features/use-feature "tokens/numeric-input")
hidden? (get values :hidden)
[{:keys [ids values]}]
(let [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))
@@ -169,17 +118,6 @@
(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)
@@ -238,9 +176,8 @@
preview-complete?))
(swap! state* assoc :selected-blend-mode current-blend-mode)))
[:section {:class (stl/css-case :element-set-content true
:hidden hidden?)
:aria-label "layer-menu-section"}
[:div {:class (stl/css-case :element-set-content true
:hidden hidden?)}
[:div {:class (stl/css :select)}
[:& select
{:default-value selected-blend-mode
@@ -250,34 +187,16 @@
:class (stl/css-case :hidden-select hidden?)
:on-pointer-enter-option handle-blend-mode-enter
:on-pointer-leave-option handle-blend-mode-leave}]]
(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 :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)}]]
[:div {:class (stl/css :actions)}

View File

@@ -6,7 +6,6 @@
@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;
@@ -44,9 +43,3 @@
}
}
}
.numeric-input-wrapper {
grid-column: span 2;
--dropdown-width: var(--7-columns-dropdown-width);
--dropdown-offset: #{px2rem(-35)};
}

View File

@@ -85,7 +85,6 @@
[:*
[:> layer-menu* {:ids ids
:type type
:applied-tokens applied-tokens
:values layer-values}]
[:> measures-menu* {:ids ids

View File

@@ -84,7 +84,6 @@
[:*
[:> layer-menu* {:ids ids
:type type
:applied-tokens applied-tokens
:values layer-values}]
[:> measures-menu* {:ids ids

View File

@@ -100,7 +100,6 @@
[:*
[:> layer-menu* {:ids ids
:type shape-type
:applied-tokens applied-tokens
:values layer-values}]
[:> measures-menu* {:ids ids
:applied-tokens applied-tokens

View File

@@ -111,7 +111,6 @@
[: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

View File

@@ -382,7 +382,7 @@
objects
objects)))
[layer-ids layer-values layer-tokens]
[layer-ids layer-values]
(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-container-tokens]
[layout-container-ids layout-container-values layout-contianer-tokens]
(get-attrs shapes objects :layout-container)
[layout-item-ids layout-item-values {}]
@@ -442,7 +442,6 @@
(when-not (empty? layer-ids)
[:> layer-menu* {:type type
:ids layer-ids
:applied-tokens layer-tokens
:values layer-values}])
(when-not (empty? measure-ids)
@@ -460,7 +459,7 @@
{:type type
:ids layout-container-ids
:values layout-container-values
:applied-tokens layout-container-tokens
:applied-tokens layout-contianer-tokens
:multiple true}]
(when (or is-layout-child? has-flex-layout-container?)

View File

@@ -84,7 +84,6 @@
[:*
[:> layer-menu* {:ids ids
:applied-tokens applied-tokens
:type type
:values layer-values}]
[:> measures-menu* {:ids ids

View File

@@ -85,7 +85,6 @@
[:*
[:> layer-menu* {:ids ids
:type type
:applied-tokens applied-tokens
:values layer-values}]
[:> measures-menu* {:ids ids
:type type

View File

@@ -125,7 +125,6 @@
[:*
[:> layer-menu* {:ids ids
:type type
:applied-tokens applied-tokens
:values layer-values}]
[:> measures-menu*
{:ids ids

View File

@@ -18,8 +18,8 @@
[app.main.data.workspace.shortcuts]
[app.main.store :as st]
[app.main.ui.components.search-bar :refer [search-bar*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.product.panel-title :refer [panel-title*]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.strings :refer [matches-search]]
@@ -487,13 +487,9 @@
(dom/focus! (dom/get-element "shortcut-search")))
[:div {:class (dm/str class " " (stl/css :shortcuts))}
[:div {:class (stl/css :shortcuts-header)}
[:div {:class (stl/css :shortcuts-title)} (tr "shortcuts.title")]
[:> icon-button* {:variant "ghost"
:icon i/close
:class (stl/css :shortcuts-close-button)
:on-click close-fn
:aria-label (tr "labels.close")}]]
[:> panel-title* {:class (stl/css :shortcuts-title)
:text (tr "shortcuts.title")
:on-close close-fn}]
[:div {:class (stl/css :search-field)}
[:> search-bar* {:on-change on-search-term-change-2

View File

@@ -18,27 +18,8 @@
margin: deprecated.$s-16 deprecated.$s-12 deprecated.$s-4 deprecated.$s-12;
}
.shortcuts-header {
@include deprecated.flexCenter;
@include deprecated.uppercaseTitleTipography;
position: relative;
height: deprecated.$s-32;
padding: deprecated.$s-2 deprecated.$s-2 deprecated.$s-2 0;
margin: deprecated.$s-4 deprecated.$s-4 0 deprecated.$s-4;
border-radius: deprecated.$br-6;
background-color: var(--panel-title-background-color);
.shortcuts-title {
@include deprecated.flexCenter;
flex-grow: 1;
color: var(--title-foreground-color-hover);
}
.shortcuts-close-button {
position: absolute;
right: 0;
top: 0;
}
.shortcuts-title {
margin: var(--sp-s) var(--sp-s) 0 var(--sp-s);
}
.section {

View File

@@ -228,7 +228,7 @@
:class (stl/css :main-toolbar-options-button)
:icon i/bug
:aria-pressed (contains? layout :debug-panel)
:aria-label "Debugging tool"
:aria-label (tr "workspace.toolbar.debug")
:tooltip-placement "bottom"
:on-click toggle-debug-panel}]])]]

View File

@@ -5471,6 +5471,10 @@ msgstr "Delete row and shapes"
msgid "workspace.context-menu.grid-track.row.duplicate"
msgstr "Duplicate row"
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
msgid "workspace.debug.title"
msgstr "Debugging tools"
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
msgid "workspace.focus.focus-mode"
msgstr "Focus mode"
@@ -8412,6 +8416,10 @@ msgstr "Comments (%s)"
msgid "workspace.toolbar.curve"
msgstr "Curve (%s)"
#: src/app/main/ui/workspace/top_toolbar.cljs:231
msgid "workspace.toolbar.debug"
msgstr "Debugging tools"
#: src/app/main/ui/workspace/top_toolbar.cljs:172
msgid "workspace.toolbar.ellipse"
msgstr "Ellipse (%s)"

View File

@@ -5452,6 +5452,10 @@ msgstr "Borrar fila con el contenido"
msgid "workspace.context-menu.grid-track.row.duplicate"
msgstr "Duplicar fila"
#: src/app/main/ui/workspace/sidebar/debug.cljs:37
msgid "workspace.debug.title"
msgstr "Herramientas de depuración"
#: src/app/main/ui/workspace/sidebar/layers.cljs:512
msgid "workspace.focus.focus-mode"
msgstr "Modo foco"
@@ -8273,6 +8277,10 @@ msgstr "Comentarios (%s)"
msgid "workspace.toolbar.curve"
msgstr "Curva (%s)"
#: src/app/main/ui/workspace/top_toolbar.cljs:231
msgid "workspace.toolbar.debug"
msgstr "Herramientas de depuración"
#: src/app/main/ui/workspace/top_toolbar.cljs:172
msgid "workspace.toolbar.ellipse"
msgstr "Elipse (%s)"