Compare commits
88 Commits
andy-docs-
...
niwinz-sub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c159dd0fe | ||
|
|
f7b5266304 | ||
|
|
09c23256b7 | ||
|
|
1ae1c0460e | ||
|
|
291c7349db | ||
|
|
b605a3b53d | ||
|
|
cc2dab2756 | ||
|
|
d0c0664338 | ||
|
|
2240f25143 | ||
|
|
93a5ec2f5d | ||
|
|
d6784771a8 | ||
|
|
930c814ded | ||
|
|
1a5a69bca2 | ||
|
|
9ad323a220 | ||
|
|
5fa4368d70 | ||
|
|
b8efd2518d | ||
|
|
7b2271ec38 | ||
|
|
2240d93069 | ||
|
|
3f4506284b | ||
|
|
af1dfd91aa | ||
|
|
24feebd73b | ||
|
|
33e5a9a538 | ||
|
|
9c69b07a62 | ||
|
|
56f5be4f37 | ||
|
|
8a70204d41 | ||
|
|
57a27f7e7f | ||
|
|
3b0b2a78d6 | ||
|
|
10bf4610df | ||
|
|
77e8414aea | ||
|
|
20ecf3b066 | ||
|
|
49b1032973 | ||
|
|
5ba7dd8c56 | ||
|
|
38b5125186 | ||
|
|
6677ae83d4 | ||
|
|
0737c055f0 | ||
|
|
4b88748fe3 | ||
|
|
92107e5b1e | ||
|
|
ebc0e3a23c | ||
|
|
ebe4f2da50 | ||
|
|
a07c1d6eaa | ||
|
|
613bfda955 | ||
|
|
f7ef6618e5 | ||
|
|
fe334d9cbe | ||
|
|
268b883c73 | ||
|
|
f6a4effa29 | ||
|
|
ced848077e | ||
|
|
7d9d318539 | ||
|
|
9781fceadb | ||
|
|
3178bd9a27 | ||
|
|
e5d677f449 | ||
|
|
6bf928893c | ||
|
|
53dd90aa24 | ||
|
|
9fd0f6a8f3 | ||
|
|
638c3356d3 | ||
|
|
6879f54e5d | ||
|
|
a71baa5a78 | ||
|
|
8e4a89bd1c | ||
|
|
7aad9da285 | ||
|
|
ab57a4ae52 | ||
|
|
266ee29bb9 | ||
|
|
69ca86bb6c | ||
|
|
ee14a845fc | ||
|
|
73639f5d16 | ||
|
|
9bd106b2bc | ||
|
|
59c75afc7b | ||
|
|
bbc81586e3 | ||
|
|
c9c30eab75 | ||
|
|
86ba9280db | ||
|
|
5800cc4bb2 | ||
|
|
aa29a34c4c | ||
|
|
3276129cc7 | ||
|
|
67a96de475 | ||
|
|
48785b4846 | ||
|
|
3f0573f95d | ||
|
|
d94a2a8881 | ||
|
|
1c237a0968 | ||
|
|
b7eaeffa88 | ||
|
|
722fcc1f82 | ||
|
|
2ad42cfd9b | ||
|
|
795f65632a | ||
|
|
7819e6c440 | ||
|
|
952f622ce9 | ||
|
|
a6c6f97f47 | ||
|
|
88424eb54a | ||
|
|
de9a21121a | ||
|
|
cea10308b7 | ||
|
|
5223c9c881 | ||
|
|
be62fa10c4 |
2
.github/workflows/build-tag.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
🐳 *[PENPOT] Docker image available: {{ github.ref_name }}*
|
||||
🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
|
||||
2
.github/workflows/commit-checker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check Commit Type
|
||||
uses: gsactions/commit-message-checker@v2
|
||||
with:
|
||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$'
|
||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
|
||||
flags: 'gm'
|
||||
error: 'Commit should match CONTRIBUTING.md guideline'
|
||||
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
||||
|
||||
23
CHANGES.md
@@ -1,5 +1,23 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.14.0 (Unreleased)
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
|
||||
- Tokens panel nested path view [Taiga #9966](https://tree.taiga.io/project/penpot/us/9966)
|
||||
- Improve usability of lock and hide buttons in the layer panel. [Taiga #12916](https://tree.taiga.io/project/penpot/issue/12916)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
## 2.13.0 (Unreleased)
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
@@ -23,6 +41,7 @@
|
||||
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
||||
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
|
||||
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
|
||||
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
|
||||
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
|
||||
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
|
||||
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
|
||||
@@ -30,7 +49,6 @@
|
||||
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
|
||||
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
|
||||
|
||||
|
||||
## 2.12.1
|
||||
|
||||
### :bug: Bugs fixed
|
||||
@@ -39,7 +57,6 @@
|
||||
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
|
||||
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
|
||||
|
||||
|
||||
## 2.12.0
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
@@ -51,7 +68,6 @@ The backend RPC API URLS are changed from `/api/rpc/command/<name>` to
|
||||
compatibility; however, if you are a user of this API, it is strongly
|
||||
recommended that you adapt your code to use the new PATH.
|
||||
|
||||
|
||||
#### Updated SSO Callback URL
|
||||
|
||||
The OAuth / Single Sign-On (SSO) callback endpoint has changed to
|
||||
@@ -84,7 +100,6 @@ This update standardizes all authentication flows under the single URL
|
||||
and makis it more modular, enabling the ability to configure SSO auth
|
||||
provider dinamically.
|
||||
|
||||
|
||||
#### Changes on default docker compose
|
||||
|
||||
We have updated the `docker/images/docker-compose.yaml` with a small
|
||||
|
||||
@@ -132,3 +132,94 @@ Some naming conventions:
|
||||
(if-let [last-period (str/last-index-of s ".")]
|
||||
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
||||
[s ""]))
|
||||
|
||||
;; Tree building functions --------------------------------------------------
|
||||
|
||||
"Build tree structure from flat list of paths"
|
||||
|
||||
"`build-tree-root` is the main function to build the tree."
|
||||
|
||||
"Receives a list of segments with 'name' properties representing paths,
|
||||
and a separator string."
|
||||
"E.g segments = [{... :name 'one/two/three'} {... :name 'one/two/four'} {... :name 'one/five'}]"
|
||||
|
||||
"Transforms into a tree structure like:
|
||||
[{:name 'one'
|
||||
:path 'one'
|
||||
:depth 0
|
||||
:leaf nil
|
||||
:children-fn (fn [] [{:name 'two'
|
||||
:path 'one.two'
|
||||
:depth 1
|
||||
:leaf nil
|
||||
:children-fn (fn [] [{... :name 'three'} {... :name 'four'}])}
|
||||
{:name 'five'
|
||||
:path 'one.five'
|
||||
:depth 1
|
||||
:leaf {... :name 'five'}
|
||||
...}])}]"
|
||||
|
||||
(defn- sort-by-children
|
||||
"Sorts segments so that those with children come first."
|
||||
[segments separator]
|
||||
(sort-by (fn [segment]
|
||||
(let [path (split-path (:name segment) :separator separator)
|
||||
path-length (count path)]
|
||||
(if (= path-length 1)
|
||||
1
|
||||
0)))
|
||||
segments))
|
||||
|
||||
(defn- group-by-first-segment
|
||||
"Groups segments by their first path segment and update segment name."
|
||||
[segments separator]
|
||||
(reduce (fn [acc segment]
|
||||
(let [[first-segment & remaining-segments] (split-path (:name segment) :separator separator)
|
||||
rest-path (when (seq remaining-segments) (join-path remaining-segments :separator separator :with-spaces? false))]
|
||||
(update acc first-segment (fnil conj [])
|
||||
(if rest-path
|
||||
(assoc segment :name rest-path)
|
||||
segment))))
|
||||
{}
|
||||
segments))
|
||||
|
||||
(defn- sort-and-group-segments
|
||||
"Sorts elements and groups them by their first path segment."
|
||||
[segments separator]
|
||||
(let [sorted (sort-by-children segments separator)
|
||||
grouped (group-by-first-segment sorted separator)]
|
||||
grouped))
|
||||
|
||||
(defn- build-tree-node
|
||||
"Builds a single tree node with lazy children."
|
||||
[segment-name remaining-segments separator parent-path depth]
|
||||
(let [current-path (if parent-path
|
||||
(str parent-path "." segment-name)
|
||||
segment-name)
|
||||
|
||||
is-leaf? (and (seq remaining-segments)
|
||||
(every? (fn [segment]
|
||||
(let [remaining-segment-name (first (split-path (:name segment) :separator separator))]
|
||||
(= segment-name remaining-segment-name)))
|
||||
remaining-segments))
|
||||
|
||||
leaf-segment (when is-leaf? (first remaining-segments))
|
||||
node {:name segment-name
|
||||
:path current-path
|
||||
:depth depth
|
||||
:leaf leaf-segment
|
||||
:children-fn (when-not is-leaf?
|
||||
(fn []
|
||||
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
|
||||
(mapv (fn [[child-segment-name remaining-child-segments]]
|
||||
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
|
||||
grouped-elements))))}]
|
||||
node))
|
||||
|
||||
(defn build-tree-root
|
||||
"Builds the root level of the tree."
|
||||
[segments separator]
|
||||
(let [grouped-elements (sort-and-group-segments segments separator)]
|
||||
(mapv (fn [[segment-name remaining-segments]]
|
||||
(build-tree-node segment-name remaining-segments separator nil 0))
|
||||
grouped-elements)))
|
||||
|
||||
@@ -47,6 +47,18 @@
|
||||
self-reference? (get token-references token-name)]
|
||||
self-reference?))
|
||||
|
||||
(defn references-token?
|
||||
"Recursively check if a value references the token name. Handles strings, maps, and sequences."
|
||||
[value token-name]
|
||||
(cond
|
||||
(string? value)
|
||||
(boolean (some #(= % token-name) (find-token-value-references value)))
|
||||
(map? value)
|
||||
(some true? (map #(references-token? % token-name) (vals value)))
|
||||
(sequential? value)
|
||||
(some true? (map #(references-token? % token-name) value))
|
||||
:else false))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -558,3 +570,18 @@
|
||||
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
|
||||
[token-value]
|
||||
(string? token-value))
|
||||
|
||||
(defn update-token-value-references
|
||||
"Recursively update token references within a token value, supporting complex token values (maps, sequences, strings)."
|
||||
[value old-name new-name]
|
||||
(cond
|
||||
(string? value)
|
||||
(str/replace value
|
||||
(re-pattern (str "\\{" (str/replace old-name "." "\\.") "\\}"))
|
||||
(str "{" new-name "}"))
|
||||
(map? value)
|
||||
(d/update-vals value #(update-token-value-references % old-name new-name))
|
||||
(sequential? value)
|
||||
(mapv #(update-token-value-references % old-name new-name) value)
|
||||
:else
|
||||
value))
|
||||
|
||||
@@ -909,7 +909,8 @@ Will return a value that matches this schema:
|
||||
`:all` All of the nested sets are active
|
||||
`:partial` Mixed active state of nested sets")
|
||||
(get-tokens-in-active-sets [_] "set of set names that are active in the the active themes")
|
||||
(get-all-tokens [_] "all tokens in the lib")
|
||||
(get-all-tokens [_] "all tokens in the lib, as a sequence")
|
||||
(get-all-tokens-map [_] "all tokens in the lib, as a map name -> token")
|
||||
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name"))
|
||||
|
||||
(declare parse-multi-set-dtcg-json)
|
||||
@@ -1306,6 +1307,10 @@ Will return a value that matches this schema:
|
||||
tokens))
|
||||
|
||||
(get-all-tokens [this]
|
||||
(mapcat #(vals (get-tokens- %))
|
||||
(get-sets this)))
|
||||
|
||||
(get-all-tokens-map [this]
|
||||
(reduce
|
||||
(fn [tokens' set]
|
||||
(into tokens' (map (fn [x] [(:name x) x]) (vals (get-tokens- set)))))
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ~/.bashrc
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
|
||||
rm -rf ./_dist
|
||||
yarn install
|
||||
yarn
|
||||
yarn run build
|
||||
|
||||
@@ -19,12 +19,6 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
|
||||
<p>Create and manage your teams</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/projects-files">
|
||||
<h2>Projects and Files →</h2>
|
||||
<p>Organize your work with projects and files</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/comments/">
|
||||
<h2>Comments →</h2>
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: Projects and Files
|
||||
order: 3
|
||||
desc: Learn how to organize your work in Penpot. Create, manage and organize projects and files, work with drafts, and handle deleted items.
|
||||
---
|
||||
|
||||
<h1 id="projects-files">Projects and Files</h1>
|
||||
<p class="main-paragraph">Projects and files are the core organizational structure in Penpot. Projects work like folders that contain multiple design files, helping you organize your work efficiently. Files are your actual design documents where you create boards, pages, and all your design elements.</p>
|
||||
<p class="main-paragraph">Understanding how to manage projects and files will help you keep your workspace organized and make it easier to collaborate with your team.</p>
|
||||
|
||||
<h2 id="projects-management">Projects</h2>
|
||||
<p>Projects are containers that help you organize and group related design files together. Think of them as folders in a file system. You can create as many projects as you need to organize your work by client, product, feature, or any other structure that fits your workflow.</p>
|
||||
<p>If you're working with others, projects should be created inside a team so that team members can collaborate on the files within them. Projects created in your personal space ("Your Penpot") remain private to you.</p>
|
||||
<figure>
|
||||
<img src="/img/files-projects/01-projects.webp" alt="Projects view in dashboard" />
|
||||
</figure>
|
||||
|
||||
<h3 id="create-project">Create a project</h3>
|
||||
<p>To create a new project, use the <strong>+ New project</strong> button in the dashboard. You can also use the keyboard shortcut <kbd>+</kbd> when you're on the dashboard. A dialog will appear where you can enter the project name. Once created, the project will appear in your projects list.</p>
|
||||
<p>When you create a project, you can immediately start adding files to it, or create files first and move them into the project later.</p>
|
||||
|
||||
<h3 id="edit-project">Edit a project</h3>
|
||||
<p>To edit a project's name, right-click on the project in the sidebar or click the three-dot menu next to the project name. Select <strong>Edit</strong> or <strong>Rename</strong> to change the project name. You can also update the project's profile picture from the same menu.</p>
|
||||
|
||||
<h3 id="pin-project">Pin a project</h3>
|
||||
<p>Projects can be pinned to the sidebar for quick access. Right-click on a project and select <strong>Pin</strong> to keep it visible in the sidebar even when you have many projects. Pinned projects appear at the top of your projects list for easy access.</p>
|
||||
<p>To unpin a project, right-click on it and select <strong>Unpin</strong>. The project will remain in your list but won't be pinned to the sidebar anymore.</p>
|
||||
<figure>
|
||||
<img src="/img/files-projects/04-pin-project.webp" alt="Pin project option" />
|
||||
</figure>
|
||||
|
||||
<h3 id="move-project">Move a project</h3>
|
||||
<p>Projects can be moved between teams. To move a project, right-click on it and select <strong>Move to</strong> from the context menu. A dialog will appear showing all available teams where you can move the project. Select the destination team and confirm the move.</p>
|
||||
<figure>
|
||||
<img src="/img/files-projects/06-move-project.webp" alt="Move project to another team" />
|
||||
</figure>
|
||||
<p>When you move a project to another team, all files within the project are moved along with it. Team members of the destination team will gain access to the project and its files according to their permissions.</p>
|
||||
<p class="advice">Moving a project to another team changes its ownership and access permissions. Make sure the destination team has the appropriate members and permissions for the work contained in the project.</p>
|
||||
|
||||
<h3 id="delete-project">Delete a project</h3>
|
||||
<p>To delete a project, right-click on it and select <strong>Delete</strong> from the menu. You'll be asked to confirm the deletion. Keep in mind that deleting a project will also delete all files within it. Make sure you have backed up any important files before deleting a project.</p>
|
||||
<p class="advice">Deleted projects and their files are moved to the trash area where they can be restored or permanently deleted.</p>
|
||||
|
||||
<h2 id="files-management">Files</h2>
|
||||
<p>Files are your design documents in Penpot. Each file contains pages, boards, and all the design elements you create. Files can be created within a project or in the drafts section, and you can move them between projects as needed.</p>
|
||||
|
||||
<h3 id="create-file">Create a file</h3>
|
||||
<p>To create a new file, you have several options:</p>
|
||||
<ul>
|
||||
<li>Click the <strong>+</strong> button in a project to create a file inside that project</li>
|
||||
<li>Use the keyboard shortcut <kbd>+</kbd> when you have a project selected</li>
|
||||
<li>Create a file directly in the drafts section if you're not ready to organize it into a project yet</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/files-projects/05-create-file.webp" alt="Create a new file" />
|
||||
</figure>
|
||||
<p>When creating a file, you'll be asked to give it a name. The file will open in the workspace where you can start designing immediately.</p>
|
||||
|
||||
<h3 id="edit-file">Edit a file</h3>
|
||||
<p>To rename a file, right-click on the file card in the dashboard and select <strong>Rename</strong>, or click on the three-dot menu on the file card. Enter the new name and confirm the change. You can also access file settings and other options from the file's context menu.</p>
|
||||
|
||||
<h3 id="move-file">Move a file</h3>
|
||||
<p>Files can be moved between projects, from drafts to a project, or even to projects in other teams. To move a file, right-click on the file card and select <strong>Move to</strong> from the context menu. A dialog will appear showing all available projects across your teams where you can choose the destination. Select the project where you want to move the file and confirm.</p>
|
||||
<p>You can also drag and drop files between projects in the dashboard for a quick way to reorganize your files within the same team.</p>
|
||||
<p>When moving a file to a project in another team, the file becomes accessible to members of that team according to their permissions. Moving a file doesn't affect its content or any shared libraries it might be using. Only its location in your project structure changes.</p>
|
||||
<p class="advice">When moving files between teams, be aware that this changes who has access to the file. Make sure the destination team has the appropriate members and permissions for the work contained in the file.</p>
|
||||
|
||||
<h3 id="duplicate-file">Duplicate a file</h3>
|
||||
<p>To create a copy of an existing file, right-click on the file card and select <strong>Duplicate</strong>. The duplicated file will be created in the same location (project or drafts) with the same name plus "Copy" added to it. You can then rename or move it as needed.</p>
|
||||
<p>Duplicating a file creates a complete copy including all pages, boards, and design elements. This is useful when you want to create variations of a design or use a file as a starting point for a new project.</p>
|
||||
|
||||
<h3 id="delete-file">Delete a file</h3>
|
||||
<p>To delete a file, right-click on the file card and select <strong>Delete</strong>. You'll be asked to confirm the deletion. The file will be moved to the trash area where it can be restored or permanently deleted later.</p>
|
||||
<p class="advice">Deleting a file doesn't immediately remove it permanently. You can recover deleted files from the trash area within a certain time period.</p>
|
||||
|
||||
<h2 id="drafts">Drafts</h2>
|
||||
<p>The drafts section is a fixed, non-deletable space in your dashboard where you can create and store files that aren't part of any specific project yet. This is useful for quick sketches, experimental designs, or files you're not ready to organize into projects.</p>
|
||||
<figure>
|
||||
<img src="/img/files-projects/02-drafts.webp" alt="Drafts section" />
|
||||
</figure>
|
||||
<p>Drafts appear in a dedicated section in the dashboard sidebar, separate from your projects. All team members can see and access files in the drafts section, depending on their permissions.</p>
|
||||
<p>You can create files directly in drafts, or move existing files from projects into drafts if you want to temporarily remove them from a project's organization. Files in drafts work exactly like files in projects - they have the same functionality and features.</p>
|
||||
<p>When you're ready to organize a file from drafts, you can move it into an appropriate project using the move option in the file's context menu.</p>
|
||||
|
||||
<h2 id="trash-area">Trash area</h2>
|
||||
<p>When you delete projects or files, they are not removed permanently. Instead, they are moved to a trash area, a dedicated space for deleted content. This allows you to recover mistakenly deleted content or permanently remove items when you're sure you don't need them anymore.</p>
|
||||
<p>The trash applies to both files and projects. Items in the trash remain there for a certain period depending on your Penpot subscription plan before being automatically deleted permanently.</p>
|
||||
|
||||
<h3 id="access-trash">Access the trash</h3>
|
||||
<p>A <strong>Trash</strong> section is accessible from the dashboard navigation. When you access it, you'll see all your deleted files and projects, each clearly labeled so you can easily identify what you want to restore or permanently delete.</p>
|
||||
<figure>
|
||||
<img src="/img/files-projects/03-trash.webp" alt="Trash area" />
|
||||
</figure>
|
||||
|
||||
<h3 id="trash-permissions">Trash permissions</h3>
|
||||
<p>Access to the trash and the actions you can perform depend on your role in the team:</p>
|
||||
<ul>
|
||||
<li><strong>Owner, Admin, and Editor:</strong> Can view the trash, restore deleted items, and permanently delete items from the trash.</li>
|
||||
<li><strong>Viewer:</strong> Cannot access the trash or manage deleted content.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="restore-items">Restore items</h3>
|
||||
<p>To restore a deleted file or project, access the trash area and find the item you want to recover. Select the item and choose <strong>Restore</strong>. The item will be restored to its original location (the project it belonged to, or the drafts section if it wasn't in a project).</p>
|
||||
|
||||
<h3 id="permanently-delete">Permanently delete items</h3>
|
||||
<p>If you're sure you don't need an item anymore, you can permanently delete it from the trash. Select the item and choose <strong>Permanently delete</strong>. This action cannot be undone, so make sure you really want to remove the item permanently.</p>
|
||||
<p class="advice">Items in the trash are automatically deleted after a certain period depending on your subscription plan. If you want to keep something, restore it before the auto-deletion period expires.</p>
|
||||
@@ -455,43 +455,6 @@ ExtraBold Italic
|
||||
<p>A <strong>Typography composite token</strong> can be applied to a full text layer to set all typography properties at once. This lets you manage complete text styles using a single token instead of combining multiple individual ones.</p>
|
||||
<p>When applying a Typography composite token to a layer, any previously applied <em>Typography composite token</em> or <em>style</em> will be detached. The same happens in reverse. Only one of them can be active at a time.</p>
|
||||
|
||||
<h3 id="design-tokens-shadow">Shadow</h3>
|
||||
<p>Shadow tokens are composite entities that encapsulate the properties of one or more shadows into a single token definition. This token can contain a single shadow or an array of multiple shadows that can be reordered.</p>
|
||||
<p>Shadow tokens support both <strong>Drop Shadow</strong> and <strong>Inner Shadow</strong> types. When creating or editing a shadow token, you can select the type of shadow you want to use. The default selection is Drop Shadow.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/37-tokens-shadow-individual.webp" alt="Shadow token creation with individual values" />
|
||||
</figure>
|
||||
|
||||
<h4 id="design-tokens-shadow-properties">Shadow properties</h4>
|
||||
<p>Each shadow within a shadow token contains a set of properties that define how the shadow appears:</p>
|
||||
<ul>
|
||||
<li><strong>Color:</strong> The color of the shadow. Accepts the same values as <a href="#design-tokens-color">color tokens</a> (Hex, RGB, RGBA, ARGB, HSL, HSLA), and you can reference existing color tokens. The color picker is available when defining the value.</li>
|
||||
<li><strong>X offset:</strong> The horizontal offset of the shadow. Can be unit or unitless, and accepts negative values. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
|
||||
<li><strong>Y offset:</strong> The vertical offset of the shadow. Can be unit or unitless, and accepts negative values. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
|
||||
<li><strong>Blur:</strong> The blur radius of the shadow. Can be unit or unitless. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
|
||||
<li><strong>Spread:</strong> The spread radius of the shadow. Can be unit or unitless. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
|
||||
<li><strong>Type:</strong> Whether the shadow is a drop shadow or an inner shadow. Selected via a dropdown menu, with Drop Shadow as the default.</li>
|
||||
</ul>
|
||||
<p>Each property within a shadow token can reference existing tokens or be assigned hardcoded values. Shadows can also reference other shadow tokens (the type of shadow must match when using references).</p>
|
||||
<p class="advice">Not all properties are mandatory to save a shadow token. Some can be empty (and will be computed as 0). Only the color property is mandatory. In an array of shadows, if any shadow does not have the color set, the form cannot be saved.</p>
|
||||
|
||||
<h4 id="design-tokens-shadow-create">Creating shadow tokens</h4>
|
||||
<p>To create a shadow token, click on the <strong>+</strong> next to <strong>Shadow</strong> in the Tokens panel. Shadow tokens can be created in two ways:</p>
|
||||
<ul>
|
||||
<li><strong>Individual values:</strong> You can create one shadow or multiple shadows with individual property values. Click the <strong>+</strong> button to add more shadows to the array. New shadows are added at the top of the list.</li>
|
||||
<li><strong>Single reference:</strong> You can reference another existing shadow token. When using a single reference, you cannot add more than one shadow. The resolved value will display the shadow or list of shadows that the referenced token contains.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/38-tokens-shadow-reference.webp" alt="Shadow token creation with reference" />
|
||||
</figure>
|
||||
<p>When creating a shadow with individual values, the color value starts empty, but the other inputs have default values (X: 4, Y: 4, Blur: 4, Spread: 0). You can reorder shadows by hovering over a shadow form and using the reorder button to drag it to a different position.</p>
|
||||
<p>You can also reference another existing shadow token instead of defining each property manually. When doing so, Penpot resolves all shadow properties from the referenced token.</p>
|
||||
|
||||
<h4 id="design-tokens-shadow-apply">Applying shadow tokens</h4>
|
||||
<p>Shadow tokens can be applied to any layer type. Clicking on a shadow token will apply it to the selected layer. Right-clicking on a shadow token shows the context menu with the <strong>Shadow</strong> option to apply it.</p>
|
||||
<p class="advice">Text elements in CSS do not support inner shadows, but Penpot does, since it uses the filter property internally instead of the box-shadow property.</p>
|
||||
<p>When applying a shadow token, any existing shadow on the layer will be overridden (whether it's a raw shadow or an applied token shadow). If the token contains an array of shadows, each shadow will be added in the same order as in the creation form.</p>
|
||||
<p class="advice">In Penpot, an element can have multiple shadows, but only one token of the same type can be applied. This means that applying a second shadow token would override the first one, regardless of how many shadows the shape currently has.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -198,10 +198,10 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||
`[id="shape-00000000-0000-0000-0000-000000000000"]`,
|
||||
);
|
||||
this.toolbarOptions = page.getByTestId("toolbar-options");
|
||||
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
|
||||
this.ellipseShapeButton = page.getByRole("button", { name: "Ellipse (E)" });
|
||||
this.moveButton = page.getByRole("button", { name: "Move (V)" });
|
||||
this.boardButton = page.getByRole("button", { name: "Board (B)" });
|
||||
this.rectShapeButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Rectangle" });
|
||||
this.ellipseShapeButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Ellipse" });
|
||||
this.moveButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Move" });
|
||||
this.boardButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Board" });
|
||||
this.toggleToolbarButton = page.getByRole("button", {
|
||||
name: "Toggle toolbar",
|
||||
});
|
||||
|
||||
@@ -189,8 +189,8 @@ test("BUG 7760 - Layout losing properties when changing parents", async ({
|
||||
await workspacePage.clickLeafLayer("Flex Board");
|
||||
|
||||
// Move the first board into the second
|
||||
const hAuto = await workspacePage.page.getByTitle("Fit content (Horizontal)");
|
||||
const vAuto = await workspacePage.page.getByTitle("Fit content (Vertical)");
|
||||
const hAuto = await workspacePage.page.getByTestId("behaviour-h-auto");
|
||||
const vAuto = await workspacePage.page.getByTestId("behaviour-v-auto");
|
||||
|
||||
await expect(vAuto.locator("input")).toBeChecked();
|
||||
await expect(hAuto.locator("input")).toBeChecked();
|
||||
|
||||
@@ -40,6 +40,7 @@ const setupEmptyTokensFile = async (page, options = {}) => {
|
||||
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
||||
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
||||
tokenSetItems: workspacePage.tokenSetItems,
|
||||
tokensSidebar: workspacePage.tokensSidebar,
|
||||
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
||||
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
||||
};
|
||||
@@ -110,15 +111,12 @@ const checkInputFieldWithError = async (
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
const checkInputFieldWithoutError = async (
|
||||
tokenThemeUpdateCreateModal,
|
||||
inputLocator,
|
||||
) => {
|
||||
const checkInputFieldWithoutError = async (inputLocator) => {
|
||||
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
||||
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
||||
};
|
||||
|
||||
async function testTokenCreationFlow(
|
||||
const testTokenCreationFlow = async (
|
||||
page,
|
||||
{
|
||||
tokenLabel,
|
||||
@@ -132,7 +130,7 @@ async function testTokenCreationFlow(
|
||||
resolvedValueText,
|
||||
secondResolvedValueText,
|
||||
},
|
||||
) {
|
||||
) => {
|
||||
const invalidValueError = "Invalid token value";
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
const selfReferenceError = "Token has self reference";
|
||||
@@ -242,7 +240,45 @@ async function testTokenCreationFlow(
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
||||
).toBeEnabled();
|
||||
}
|
||||
};
|
||||
|
||||
const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
|
||||
const tokenSegments = tokenName.split(".");
|
||||
const tokenFolderTree = tokenSegments.slice(0, -1);
|
||||
const tokenLeafName = tokenSegments.pop();
|
||||
|
||||
const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`);
|
||||
const typeSectionButton = typeParentWrapper
|
||||
.getByRole("button", {
|
||||
name: type,
|
||||
})
|
||||
.first();
|
||||
|
||||
const isSectionExpanded =
|
||||
await typeSectionButton.getAttribute("aria-expanded");
|
||||
|
||||
if (isSectionExpanded === "false") {
|
||||
await typeSectionButton.click();
|
||||
}
|
||||
|
||||
for (const segment of tokenFolderTree) {
|
||||
const segmentButton = typeParentWrapper
|
||||
.getByRole("listitem")
|
||||
.getByRole("button", { name: segment })
|
||||
.first();
|
||||
|
||||
const isExpanded = await segmentButton.getAttribute("aria-expanded");
|
||||
if (isExpanded === "false") {
|
||||
await segmentButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
await expect(
|
||||
typeParentWrapper.getByRole("button", {
|
||||
name: tokenLeafName,
|
||||
}),
|
||||
).toBeEnabled();
|
||||
};
|
||||
|
||||
test.describe("Tokens: Tokens Tab", () => {
|
||||
test("Clicking tokens tab button opens tokens sidebar tab", async ({
|
||||
@@ -398,15 +434,12 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
const selfReferenceError = "Token has self reference";
|
||||
const missingReferenceError = "Missing token references";
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
const addTokenButton = tokensTabPanel.getByRole("button", {
|
||||
name: `Add Token: Color`,
|
||||
});
|
||||
|
||||
await addTokenButton.click();
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
// Placeholder checks
|
||||
@@ -471,38 +504,34 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", {
|
||||
name: "color.primary",
|
||||
}),
|
||||
).toBeEnabled();
|
||||
await unfoldTokenTree(tokensSidebar, "color", "color.primary");
|
||||
|
||||
// Create token referencing the previous one with keyboard
|
||||
|
||||
await tokensTabPanel
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
await nameField.click();
|
||||
await nameField.fill("color.secondary");
|
||||
await nameField.fill("secondary");
|
||||
await nameField.press("Tab");
|
||||
|
||||
await valueField.click();
|
||||
await valueField.fill("{color.primary}");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await nameField.press("Enter");
|
||||
await submitButton.press("Enter");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", {
|
||||
name: "color.secondary",
|
||||
tokensSidebar.getByRole("button", {
|
||||
name: "secondary",
|
||||
}),
|
||||
).toBeEnabled();
|
||||
|
||||
// Tokens tab panel should have two tokens with the color red / #ff0000
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "#ff0000" }),
|
||||
tokensSidebar.getByRole("button", { name: "#ff0000" }),
|
||||
).toHaveCount(2);
|
||||
|
||||
// Global set has been auto created and is active
|
||||
@@ -518,7 +547,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
).toHaveAttribute("aria-checked", "true");
|
||||
|
||||
// Check color picker
|
||||
await tokensTabPanel
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
@@ -1079,7 +1108,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
await setupEmptyTokensFile(page, {flags: ["enable-token-shadow"]});
|
||||
await setupEmptyTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
// Open modal
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
@@ -1507,24 +1536,15 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
test("User edits token and auto created set show up in the sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
workspacePage,
|
||||
tokensUpdateCreateModal,
|
||||
tokenThemesSetsSidebar,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page);
|
||||
const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||
name: "Color 92",
|
||||
});
|
||||
await expect(tokensColorGroup).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "colors.blue.100",
|
||||
name: "100",
|
||||
});
|
||||
await expect(colorToken).toBeVisible();
|
||||
await colorToken.click({ button: "right" });
|
||||
@@ -1541,8 +1561,10 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
|
||||
|
||||
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
||||
name: "colors.blue.100.changed",
|
||||
name: "changed",
|
||||
});
|
||||
await expect(colorTokenChanged).toBeVisible();
|
||||
});
|
||||
@@ -1633,11 +1655,10 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
});
|
||||
|
||||
test("User creates grouped color token", async ({ page }) => {
|
||||
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
|
||||
await setupEmptyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
|
||||
@@ -1649,7 +1670,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
|
||||
await nameField.click();
|
||||
await nameField.fill("color.dark.primary");
|
||||
await nameField.fill("dark.primary");
|
||||
|
||||
await valueField.click();
|
||||
await valueField.fill("red");
|
||||
@@ -1660,7 +1681,9 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
|
||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
||||
|
||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant create regular token with value missing", async ({
|
||||
@@ -1676,7 +1699,6 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
@@ -1686,7 +1708,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
|
||||
// Fill in name but leave value empty
|
||||
await nameField.click();
|
||||
await nameField.fill("color.primary");
|
||||
await nameField.fill("primary");
|
||||
|
||||
// Submit button should remain disabled when value is empty
|
||||
await expect(submitButton).toBeDisabled();
|
||||
@@ -1704,7 +1726,6 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
.click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
|
||||
await valueField.click();
|
||||
@@ -1754,15 +1775,10 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||
name: "Color 92",
|
||||
});
|
||||
|
||||
await expect(tokensColorGroup).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "colors.blue.100",
|
||||
name: "100",
|
||||
});
|
||||
|
||||
await colorToken.click({ button: "right" });
|
||||
@@ -1782,15 +1798,10 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
||||
name: "Color 92",
|
||||
});
|
||||
await expect(tokensColorGroup).toBeVisible();
|
||||
|
||||
await tokensColorGroup.click();
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "colors.blue.100",
|
||||
name: "100",
|
||||
});
|
||||
await expect(colorToken).toBeVisible();
|
||||
await colorToken.click({ button: "right" });
|
||||
@@ -1803,8 +1814,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
});
|
||||
|
||||
test("User fold/unfold color tokens", async ({ page }) => {
|
||||
const { tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFile(page);
|
||||
const { tokensSidebar } = await setupTokensFile(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
@@ -1814,8 +1824,10 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
await expect(tokensColorGroup).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "colors.blue.100",
|
||||
name: "100",
|
||||
});
|
||||
await expect(colorToken).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
@@ -2218,13 +2230,10 @@ test.describe("Tokens: Apply token", () => {
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button")
|
||||
.filter({ hasText: "Color" })
|
||||
.click();
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.black");
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "colors.black" })
|
||||
.getByRole("button", { name: "black" })
|
||||
.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Fill").click();
|
||||
|
||||
@@ -2409,10 +2418,12 @@ test.describe("Tokens: Apply token", () => {
|
||||
await nameField.fill(newTokenTitle);
|
||||
|
||||
const referenceTabButton =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
tokensUpdateCreateModal.getByRole('button', { name: 'Use a reference' });
|
||||
referenceTabButton.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
|
||||
const referenceField = tokensUpdateCreateModal.getByRole('textbox', {
|
||||
name: 'Reference'
|
||||
});
|
||||
await referenceField.fill("{Full}");
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
@@ -2462,7 +2473,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("shadow.primary");
|
||||
await nameField.fill("primary");
|
||||
|
||||
// User adds first shadow with a color from the color ramp
|
||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
@@ -2709,9 +2720,11 @@ test.describe("Tokens: Apply token", () => {
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "shadow", "primary");
|
||||
|
||||
// Verify token appears in sidebar
|
||||
const shadowToken = tokensSidebar.getByRole("button", {
|
||||
name: "shadow.primary",
|
||||
name: "primary",
|
||||
});
|
||||
await expect(shadowToken).toBeEnabled();
|
||||
|
||||
@@ -2729,3 +2742,626 @@ test.describe("Tokens: Apply token", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Tokens: Remapping Feature", () => {
|
||||
test.describe("Box Shadow Token Remapping", () => {
|
||||
test("User renames box shadow token with alias references", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base shadow token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("base-shadow");
|
||||
|
||||
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await colorField.fill("#000000");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived shadow token that references base-shadow
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
|
||||
await nameField.fill("derived-shadow");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
|
||||
await referenceField.fill("{base-shadow}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename base-shadow token
|
||||
const baseToken = tokensSidebar.getByRole("button", {
|
||||
name: "base-shadow",
|
||||
});
|
||||
await baseToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("foundation-shadow");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Check for remapping modal
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
await expect(remappingModal).toContainText("1");
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "foundation-shadow" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "derived-shadow" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User renames and updates shadow token - referenced token and applied shapes update", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
workspacePage,
|
||||
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base shadow token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("primary-shadow");
|
||||
|
||||
let colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await colorField.fill("#000000");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived shadow token that references base
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Shadow" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("card-shadow");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
|
||||
await referenceField.fill("{primary-shadow}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Apply the referenced token to a shape
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Button" })
|
||||
.click();
|
||||
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
const cardShadowToken = tokensSidebar.getByRole("button", {
|
||||
name: "card-shadow",
|
||||
});
|
||||
await cardShadowToken.click();
|
||||
|
||||
// Rename and update value of base token
|
||||
const primaryToken = tokensSidebar.getByRole("button", {
|
||||
name: "primary-shadow",
|
||||
});
|
||||
await primaryToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("main-shadow");
|
||||
|
||||
// Update the color value
|
||||
colorField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await colorField.fill("#FF0000");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Confirm remapping
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify base token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "main-shadow" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify referenced token still exists
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "card-shadow" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the shape still has the token applied with the NEW name
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Button" })
|
||||
.click();
|
||||
|
||||
// Verify the shape still has the shadow applied with the UPDATED color value
|
||||
// Expand the shadow section to access the color field
|
||||
const shadowSection = workspacePage.rightSidebar.getByTestId("shadow-section");
|
||||
await expect(shadowSection).toBeVisible();
|
||||
|
||||
// Click to expand the shadow options (the menu button)
|
||||
const shadowMenuButton = shadowSection
|
||||
.getByRole("button", { name: "options" })
|
||||
.first();
|
||||
await shadowMenuButton.click();
|
||||
|
||||
// Wait for the advanced options to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify the color value has updated from #000000 to #FF0000
|
||||
const colorInput = shadowSection.getByRole("textbox", { name: "Color" });
|
||||
expect(colorInput).not.toBeNull();
|
||||
const colorValue = await colorInput.inputValue();
|
||||
expect(colorValue.toUpperCase()).toBe("FF0000");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Typography Token Remapping", () => {
|
||||
test("User renames typography token with alias references", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("base-text");
|
||||
|
||||
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font size",
|
||||
});
|
||||
await fontSizeField.fill("16");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
|
||||
await nameField.fill("body-text");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"})
|
||||
await referenceField.fill("{base-text}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename base token
|
||||
const baseToken = tokensSidebar.getByRole("button", {
|
||||
name: "base-text",
|
||||
});
|
||||
await baseToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("default-text");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Check for remapping modal
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "default-text" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "body-text" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User renames and updates typography token - referenced token and applied shapes update", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
workspacePage,
|
||||
} = await setupTypographyTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("body-style");
|
||||
|
||||
let fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font size",
|
||||
});
|
||||
await fontSizeField.fill("16");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived typography token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Typography" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
|
||||
await nameField.fill("paragraph-style");
|
||||
|
||||
const referenceToggle =
|
||||
tokensUpdateCreateModal.getByTestId("reference-opt");
|
||||
await referenceToggle.click();
|
||||
|
||||
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
|
||||
await referenceField.fill("{body-style}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Apply the referenced token to a text shape
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Some Text" })
|
||||
.click();
|
||||
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
const paragraphToken = tokensSidebar.getByRole("button", {
|
||||
name: "paragraph-style",
|
||||
});
|
||||
await paragraphToken.click();
|
||||
|
||||
// Rename and update value of base token
|
||||
const bodyToken = tokensSidebar.getByRole("button", {
|
||||
name: "body-style",
|
||||
});
|
||||
await bodyToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("text-base");
|
||||
|
||||
// Update the font size value
|
||||
fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font size",
|
||||
});
|
||||
await fontSizeField.fill("18");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Confirm remapping
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify base token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "text-base" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify referenced token still exists
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "paragraph-style" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the text shape still has the token applied with NEW name and value
|
||||
await page.getByRole("tab", { name: "Layers" }).click();
|
||||
await workspacePage.layers
|
||||
.getByTestId("layer-row")
|
||||
.filter({ hasText: "Some Text" })
|
||||
.click();
|
||||
|
||||
// Verify the shape shows the updated font size value (18)
|
||||
// This proves the remapping worked and the value update propagated through the reference
|
||||
const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", {
|
||||
name: "Font Size",
|
||||
});
|
||||
await expect(fontSizeInput).toBeVisible();
|
||||
await expect(fontSizeInput).toHaveValue("18");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Border Radius Token Remapping", () => {
|
||||
test("User renames border radius token with alias references", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("base-radius");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField.fill("4");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("card-radius");
|
||||
|
||||
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField2.fill("{base-radius}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename base token
|
||||
const baseToken = tokensSidebar.getByRole("button", {
|
||||
name: "base-radius",
|
||||
});
|
||||
await baseToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("primary-radius");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Check for remapping modal
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "primary-radius" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "card-radius" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("User renames and updates border radius token - referenced token updates", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFile(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Create base border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
let nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("radius-sm");
|
||||
|
||||
let valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField.fill("4");
|
||||
|
||||
let submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Create derived border radius token
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Border Radius" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("button-radius");
|
||||
|
||||
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField2.fill("{radius-sm}");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
// Rename and update value of base token
|
||||
const radiusToken = tokensSidebar.getByRole("button", {
|
||||
name: "radius-sm",
|
||||
});
|
||||
await radiusToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("radius-base");
|
||||
|
||||
// Update the value
|
||||
valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await valueField.fill("8");
|
||||
|
||||
submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await submitButton.click();
|
||||
|
||||
// Confirm remapping
|
||||
const remappingModal = page.getByTestId("token-remapping-modal");
|
||||
await expect(remappingModal).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const confirmButton = remappingModal.getByRole("button", {
|
||||
name: /remap/i,
|
||||
});
|
||||
await confirmButton.click();
|
||||
|
||||
// Verify base token was renamed
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "radius-base" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify referenced token still exists
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "button-radius" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the referenced token now points to the renamed token
|
||||
// by opening it and checking the reference
|
||||
const buttonRadiusToken = tokensSidebar.getByRole("button", {
|
||||
name: "button-radius",
|
||||
});
|
||||
await buttonRadiusToken.click({ button: "right" });
|
||||
await tokenContextMenuForToken.getByText("Edit token").click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
const currentValue = tokensUpdateCreateModal.getByLabel("Value");
|
||||
await expect(currentValue).toHaveValue("{radius-base}");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -332,24 +332,33 @@ test("Copy/paste properties", async ({ page, context }) => {
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page.getByText("Rectangle").first().click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page.getByText("Board").nth(2).click({ button: "right" });
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.getByText("Rectangle")
|
||||
.first()
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.locator("div")
|
||||
.filter({ hasText: "Path" })
|
||||
.getByText("Board")
|
||||
.nth(1)
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page.getByText("Ellipse").click({ button: "right" });
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.getByText("Path")
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
|
||||
await page
|
||||
.getByTestId("layer-item")
|
||||
.getByText("Ellipse")
|
||||
.click({ button: "right" });
|
||||
await page.getByText("Copy/Paste as").hover();
|
||||
await page.getByText("Paste properties").click();
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
$weight: unquote("normal"),
|
||||
$style: string.unquote("normal")
|
||||
) {
|
||||
$filepath: "/fonts/" + $file;
|
||||
$filepath: "../fonts/" + $file;
|
||||
|
||||
@font-face {
|
||||
font-family: "#{$style-name}";
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
@mixin font-face-variable($style-name, $file, $unicode-range) {
|
||||
$filepath: "/fonts/" + $file;
|
||||
$filepath: "../fonts/" + $file;
|
||||
|
||||
@font-face {
|
||||
font-family: "#{$style-name}";
|
||||
|
||||
@@ -245,13 +245,6 @@
|
||||
--assets-component-second-border-selected: var(--color-background-primary);
|
||||
--assets-component-hightlight: var(--color-accent-secondary);
|
||||
|
||||
--radio-btns-background-color: var(--color-background-tertiary);
|
||||
--radio-btn-background-color-selected: var(--color-background-quaternary);
|
||||
--radio-btn-foreground-color: var(--color-foreground-secondary);
|
||||
--radio-btn-foreground-color-selected: var(--color-accent-primary);
|
||||
--radio-btn-border-color: var(--color-background-tertiary);
|
||||
--radio-btn-border-color-selected: var(--color-background-quaternary);
|
||||
|
||||
--library-name-foreground-color: var(--color-foreground-primary);
|
||||
--library-content-foreground-color: var(--color-foreground-secondary);
|
||||
|
||||
@@ -424,13 +417,6 @@
|
||||
--tab-border-color: var(--color-background-tertiary);
|
||||
--tab-border-color-selected: var(--color-background-secondary);
|
||||
|
||||
--radio-btns-background-color: var(--color-background-tertiary);
|
||||
--radio-btn-background-color-selected: var(--color-background-primary);
|
||||
--radio-btn-foreground-color: var(--color-foreground-secondary);
|
||||
--radio-btn-foreground-color-selected: var(--color-accent-primary);
|
||||
--radio-btn-border-color: var(--color-background-tertiary);
|
||||
--radio-btn-border-color-selected: var(--color-background-secondary);
|
||||
|
||||
--button-icon-background-color-selected: var(--color-background-primary);
|
||||
--button-icon-border-color-selected: var(--color-background-secondary);
|
||||
|
||||
|
||||
@@ -110,12 +110,9 @@
|
||||
|
||||
(defn- normalize-uri
|
||||
[uri-str]
|
||||
(let [uri (u/uri uri-str)]
|
||||
;; Ensure that the path always ends with "/"; this ensures that
|
||||
;; all path join operations works as expected.
|
||||
(cond-> uri
|
||||
(not (str/ends-with? (:path uri) "/"))
|
||||
(update :path #(str % "/")))))
|
||||
;; Ensure that the path always ends with "/"; this ensures that
|
||||
;; all path join operations works as expected.
|
||||
(u/ensure-path-slash uri-str))
|
||||
|
||||
(def public-uri
|
||||
(normalize-uri (or (obj/get global "penpotPublicURI")
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
(when unknown-tokens
|
||||
(st/emit! (show-unknown-types-warning unknown-tokens)))
|
||||
(try
|
||||
(->> (ctob/get-all-tokens tokens-lib)
|
||||
(->> (ctob/get-all-tokens-map tokens-lib)
|
||||
(sd/resolve-tokens-with-verbose-errors)
|
||||
(rx/map (fn [_]
|
||||
tokens-lib))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.tokens :as clt]
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.uuid :as uuid]
|
||||
@@ -22,6 +23,7 @@
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(declare set-selected-token-set-id)
|
||||
@@ -460,12 +462,35 @@
|
||||
;; TOKEN UI OPS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn set-token-type-section-open
|
||||
[token-type open?]
|
||||
(ptk/reify ::set-token-type-section-open
|
||||
(defn clean-tokens-paths
|
||||
[]
|
||||
(ptk/reify ::clean-tokens-paths
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-tokens :open-status-by-type] assoc token-type open?))))
|
||||
(assoc-in state [:workspace-tokens :unfolded-token-paths] []))))
|
||||
|
||||
(defn toggle-token-path
|
||||
[path]
|
||||
(ptk/reify ::toggle-token-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-tokens :unfolded-token-paths]
|
||||
(fn [paths]
|
||||
(let [paths (or paths [])]
|
||||
(if (some #(= % path) paths)
|
||||
(vec (remove #(or (= % path)
|
||||
(str/starts-with? % (str path ".")))
|
||||
paths))
|
||||
(let [split-path (cpn/split-path path :separator ".")
|
||||
partial-paths (reduce
|
||||
(fn [acc segment]
|
||||
(let [new-acc (if (empty? acc)
|
||||
segment
|
||||
(str (last acc) "." segment))]
|
||||
(conj acc new-acc)))
|
||||
[]
|
||||
split-path)]
|
||||
(into paths partial-paths)))))))))
|
||||
|
||||
(defn assign-token-context-menu
|
||||
[{:keys [position] :as params}]
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
[clojure.set :as set]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||
(l/set-level! :warn)
|
||||
|
||||
;; Helpers ---------------------------------------------------------------------
|
||||
|
||||
;; TODO: see if this can be replaced by more standard functions
|
||||
|
||||
177
frontend/src/app/main/data/workspace/tokens/remapping.cljs
Normal file
@@ -0,0 +1,177 @@
|
||||
;; 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.data.workspace.tokens.remapping
|
||||
"Core logic for token remapping functionality"
|
||||
(:require
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.logging :as log]
|
||||
[app.common.types.container :refer [shapes-seq]]
|
||||
[app.common.types.file :refer [object-containers-seq]]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dh]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||
(log/set-level! :warn)
|
||||
|
||||
;; Token Reference Scanning
|
||||
;; ========================
|
||||
|
||||
(defn scan-shape-applied-tokens
|
||||
"Scan a shape for applied token references to a specific token name"
|
||||
[shape token-name container]
|
||||
(when-let [applied-tokens (:applied-tokens shape)]
|
||||
(for [[attribute applied-token-name] applied-tokens
|
||||
:when (= applied-token-name token-name)]
|
||||
{:type :applied-token
|
||||
:shape-id (:id shape)
|
||||
:attribute attribute
|
||||
:token-name applied-token-name
|
||||
:container container})))
|
||||
|
||||
(defn scan-token-value-references
|
||||
"Scan a token value for references to a specific token name (alias), supporting complex token values."
|
||||
[token token-name]
|
||||
(letfn [(find-all-token-value-references [token-value]
|
||||
(cond
|
||||
(string? token-value)
|
||||
(filter #(= % token-name) (cto/find-token-value-references token-value))
|
||||
|
||||
(map? token-value)
|
||||
(mapcat find-all-token-value-references (vals token-value))
|
||||
|
||||
(sequential? token-value)
|
||||
(mapcat find-all-token-value-references token-value)
|
||||
|
||||
:else
|
||||
[]))]
|
||||
(when-let [value (:value token)]
|
||||
(for [referenced-token-name (find-all-token-value-references value)]
|
||||
{:type :token-alias
|
||||
:source-token-id (:id token)
|
||||
:referenced-token-name referenced-token-name}))))
|
||||
|
||||
(defn scan-workspace-token-references
|
||||
"Scan entire workspace for all token references to a specific token"
|
||||
[file-data old-token-name]
|
||||
(let [tokens-lib (:tokens-lib file-data)
|
||||
containers (object-containers-seq file-data)
|
||||
|
||||
;; Scan all shapes for applied token references to the specific token
|
||||
matching-applied (mapcat (fn [container]
|
||||
(let [shapes (shapes-seq container)]
|
||||
(mapcat #(scan-shape-applied-tokens % old-token-name container) shapes)))
|
||||
containers)
|
||||
|
||||
;; Scan tokens library for alias references to the specific token
|
||||
matching-aliases (if tokens-lib
|
||||
(let [all-tokens (ctob/get-all-tokens tokens-lib)]
|
||||
(mapcat #(scan-token-value-references % old-token-name) all-tokens))
|
||||
[])]
|
||||
(log/info :hint "token-scan-details"
|
||||
:token-name old-token-name
|
||||
:containers-count (count containers)
|
||||
:total-applied-refs (count matching-applied)
|
||||
:matching-applied (count matching-applied)
|
||||
:total-alias-refs (count matching-aliases)
|
||||
:matching-aliases (count matching-aliases))
|
||||
|
||||
{:applied-tokens matching-applied
|
||||
:token-aliases matching-aliases
|
||||
:total-references (+ (count matching-applied) (count matching-aliases))}))
|
||||
|
||||
;; Token Remapping Core Logic
|
||||
;; ==========================
|
||||
|
||||
(defn remap-tokens
|
||||
"Main function to remap all token references when a token name changes"
|
||||
[old-token-name new-token-name]
|
||||
(ptk/reify ::remap-tokens
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-data (dh/lookup-file-data state)
|
||||
scan-results (scan-workspace-token-references file-data old-token-name)
|
||||
tokens-lib (:tokens-lib file-data)
|
||||
sets (ctob/get-sets tokens-lib)
|
||||
tokens-with-sets (mapcat (fn [set]
|
||||
(map (fn [token]
|
||||
{:token token :set set})
|
||||
(vals (ctob/get-tokens tokens-lib (ctob/get-id set)))))
|
||||
sets)
|
||||
|
||||
;; Group applied token references by container
|
||||
refs-by-container (group-by :container (:applied-tokens scan-results))
|
||||
|
||||
;; Use apply-token logic to update shapes for both direct and alias references
|
||||
shape-changes (reduce-kv
|
||||
(fn [changes container refs]
|
||||
(let [shape-ids (map :shape-id refs)
|
||||
;; Find the correct token to apply (new or alias)
|
||||
token (or (some #(when (= (:name (:token %)) new-token-name) %) tokens-with-sets)
|
||||
(some #(when (= (:name (:token %)) old-token-name) %) tokens-with-sets))
|
||||
attributes (set (map :attribute refs))]
|
||||
(if token
|
||||
(-> (pcb/with-container changes container)
|
||||
(pcb/update-shapes shape-ids
|
||||
(fn [shape]
|
||||
(update shape :applied-tokens
|
||||
#(merge % (cft/attributes-map attributes (:token token)))))))
|
||||
changes)))
|
||||
(-> (pcb/empty-changes)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/with-library-data file-data))
|
||||
refs-by-container)
|
||||
|
||||
;; Create changes for updating token alias references
|
||||
token-changes (reduce
|
||||
(fn [changes ref]
|
||||
(let [source-token-id (:source-token-id ref)]
|
||||
(when-let [{:keys [token set]} (some #(when (= (:id (:token %)) source-token-id) %) tokens-with-sets)]
|
||||
(let [old-value (:value token)
|
||||
new-value (cto/update-token-value-references old-value old-token-name new-token-name)]
|
||||
(pcb/set-token changes (ctob/get-id set) (:id token)
|
||||
(assoc token :value new-value))))))
|
||||
shape-changes
|
||||
(:token-aliases scan-results))]
|
||||
|
||||
(log/info :hint "token-remapping"
|
||||
:old-name old-token-name
|
||||
:new-name new-token-name
|
||||
:references-count (:total-references scan-results))
|
||||
|
||||
(rx/of (dch/commit-changes token-changes))))))
|
||||
|
||||
(defn validate-token-remapping
|
||||
"Validate that a token remapping operation is safe to perform"
|
||||
[old-name new-name]
|
||||
(cond
|
||||
(str/blank? new-name)
|
||||
{:valid? false
|
||||
:error :invalid-name
|
||||
:message "Token name cannot be empty"}
|
||||
(= old-name new-name)
|
||||
{:valid? false
|
||||
:error :no-change
|
||||
:message "New name is the same as current name"}
|
||||
:else
|
||||
{:valid? true}))
|
||||
|
||||
(defn count-token-references
|
||||
"Count the number of references to a token in the workspace"
|
||||
[file-data token-name]
|
||||
(let [scan-results (scan-workspace-token-references file-data token-name)]
|
||||
(log/info :hint "token-reference-scan"
|
||||
:token-name token-name
|
||||
:applied-refs (count (:applied-tokens scan-results))
|
||||
:alias-refs (count (:token-aliases scan-results))
|
||||
:total (:total-references scan-results))
|
||||
(:total-references scan-results)))
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
@@ -19,7 +20,6 @@
|
||||
[app.util.object :as obj]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[lambdaisland.uri :as u]
|
||||
[okulary.core :as l]
|
||||
[promesa.core :as p]))
|
||||
|
||||
@@ -138,13 +138,13 @@
|
||||
"&display=block")]
|
||||
(dm/str
|
||||
(-> cf/public-uri
|
||||
(assoc :path "/internal/gfonts/css")
|
||||
(u/join "internal/gfonts/css")
|
||||
(assoc :query query)))))
|
||||
|
||||
(defn- process-gfont-css
|
||||
[css]
|
||||
(let [base (dm/str (assoc cf/public-uri :path "/internal/gfonts/font"))]
|
||||
(str/replace css "https://fonts.gstatic.com/s" base)))
|
||||
(let [base (u/join cf/public-uri "internal/gfonts/font")]
|
||||
(str/replace css "https://fonts.gstatic.com/s" (dm/str base))))
|
||||
|
||||
(defn- fetch-gfont-css
|
||||
[url]
|
||||
@@ -178,7 +178,9 @@
|
||||
|
||||
(defn- asset-id->uri
|
||||
[asset-id]
|
||||
(str (u/join cf/public-uri "assets/by-id/" asset-id)))
|
||||
(-> cf/public-uri
|
||||
(u/join "assets/by-id/" asset-id)
|
||||
(str)))
|
||||
|
||||
(defn generate-custom-font-variant-css
|
||||
[family variant]
|
||||
@@ -370,7 +372,7 @@
|
||||
:else
|
||||
(let [{:keys [weight style suffix]} (get-variant font font-variant-id)
|
||||
suffix (or suffix font-variant-id)
|
||||
params {:uri (dm/str cf/public-uri "fonts/" family "-" suffix ".woff")
|
||||
params {:uri (str (u/join cf/public-uri (str "fonts/" family "-" suffix ".woff")))
|
||||
:family family
|
||||
:style style
|
||||
:weight weight}]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.util.dom :as dom]
|
||||
@@ -26,7 +27,9 @@
|
||||
(defonce instance nil)
|
||||
(defonce msgbus (rx/subject))
|
||||
(defonce origin
|
||||
(dm/str (assoc cf/rasterizer-uri :path "/rasterizer.html")))
|
||||
(-> cf/rasterizer-uri
|
||||
(u/join "rasterizer.html")
|
||||
(dm/str)))
|
||||
|
||||
(declare send-message!)
|
||||
|
||||
@@ -129,7 +132,9 @@
|
||||
(dom/append-child! js/document.body iframe)
|
||||
(set! instance iframe))
|
||||
|
||||
(let [new-origin (dm/str (assoc cf/public-uri :path "/rasterizer.html"))]
|
||||
(let [new-origin (-> cf/public-uri
|
||||
(u/join "rasterizer.html")
|
||||
(dm/str))]
|
||||
(log/warn :hint "fallback to main domain" :origin new-origin)
|
||||
|
||||
(dom/set-attribute! iframe "src" new-origin)
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc file-uploader
|
||||
(mf/defc file-uploader*
|
||||
{::mf/forward-ref true}
|
||||
[{:keys [accept multi label-text label-class input-id on-selected data-testid] :as props} input-ref]
|
||||
[{:keys [accept multi label-text label-class input-id on-selected data-testid]} input-ref]
|
||||
(let [opt-pick-one #(if multi % (first %))
|
||||
|
||||
on-files-selected
|
||||
|
||||
@@ -315,7 +315,8 @@
|
||||
gap: deprecated.$s-4;
|
||||
max-height: deprecated.$s-136;
|
||||
padding: deprecated.$s-4 0;
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
|
||||
.selected-item {
|
||||
.around {
|
||||
@include deprecated.flexRow;
|
||||
|
||||
@@ -1,107 +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.components.radio-buttons
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def context
|
||||
(mf/create-context nil))
|
||||
|
||||
(mf/defc radio-button
|
||||
{::mf/props :obj}
|
||||
[{:keys [icon id value disabled title icon-class type]}]
|
||||
(let [context (mf/use-ctx context)
|
||||
allow-empty (unchecked-get context "allow-empty")
|
||||
type (if ^boolean type
|
||||
type
|
||||
(if ^boolean allow-empty
|
||||
"checkbox"
|
||||
"radio"))
|
||||
|
||||
on-change (unchecked-get context "on-change")
|
||||
selected (unchecked-get context "selected")
|
||||
name (unchecked-get context "name")
|
||||
|
||||
encode-fn (unchecked-get context "encode-fn")
|
||||
checked? (= selected value)
|
||||
|
||||
value (encode-fn value)]
|
||||
|
||||
|
||||
[:label {:html-for id
|
||||
:data-testid id
|
||||
:title title
|
||||
:class (stl/css-case
|
||||
:radio-icon true
|
||||
:checked checked?
|
||||
:disabled disabled)}
|
||||
|
||||
(if (some? icon)
|
||||
[:> icon* {:icon-id icon :class icon-class :aria-hidden true}]
|
||||
[:span {:class (stl/css :title-name)} value])
|
||||
|
||||
[:input {:id id
|
||||
:on-change on-change
|
||||
:type type
|
||||
:name name
|
||||
:disabled disabled
|
||||
:value value
|
||||
:default-checked checked?}]]))
|
||||
|
||||
(mf/defc radio-buttons
|
||||
{::mf/props :obj}
|
||||
[{:keys [name children on-change selected class wide encode-fn decode-fn allow-empty] :as props}]
|
||||
(let [encode-fn (d/nilv encode-fn identity)
|
||||
decode-fn (d/nilv decode-fn identity)
|
||||
nitems (if (array? children)
|
||||
(count (keep identity children))
|
||||
1)
|
||||
;; FIXME: we should handle this with CSS
|
||||
width (mf/with-memo [nitems]
|
||||
(if (= wide true)
|
||||
"unset"
|
||||
(fmt/format-pixels
|
||||
(+ (* 4 (- nitems 1))
|
||||
(* 32 nitems)))))
|
||||
|
||||
on-change'
|
||||
(mf/use-fn
|
||||
(mf/deps selected on-change)
|
||||
(fn [event]
|
||||
(let [input (dom/get-target event)
|
||||
value (dom/get-target-val event)
|
||||
|
||||
;; Only allow null values when the "allow-empty" prop is true
|
||||
value (when (or (not allow-empty)
|
||||
(not= value selected)) value)]
|
||||
(when (fn? on-change)
|
||||
(on-change (decode-fn value) event))
|
||||
(dom/blur! input))))
|
||||
|
||||
context-value
|
||||
(mf/spread-object props
|
||||
;; We pass a special metadata for disable
|
||||
;; key casing transformation in this
|
||||
;; concrete case, because this component
|
||||
;; uses legacy mode and props are in
|
||||
;; kebab-case style
|
||||
^{::mf/transform false}
|
||||
{:on-change on-change'
|
||||
:encode-fn encode-fn
|
||||
:decode-fn decode-fn})]
|
||||
|
||||
[:& (mf/provider context) {:value context-value}
|
||||
[:div {:class (dm/str class " " (stl/css :radio-btn-wrapper))
|
||||
:style {:width width}
|
||||
:key (dm/str name "-" selected)}
|
||||
children]]))
|
||||
@@ -1,79 +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 "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.radio-btn-wrapper {
|
||||
@include deprecated.flexCenter;
|
||||
border-radius: deprecated.$br-8;
|
||||
height: deprecated.$s-32;
|
||||
background-color: var(--input-background-color);
|
||||
gap: deprecated.$s-4;
|
||||
}
|
||||
|
||||
.radio-icon {
|
||||
--radio-icon-border-color: var(--radio-btn-border-color);
|
||||
|
||||
@include deprecated.buttonStyle;
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.focusRadio;
|
||||
height: deprecated.$s-32;
|
||||
flex-grow: 1;
|
||||
border-radius: deprecated.$s-8;
|
||||
box-sizing: border-box;
|
||||
border: deprecated.$br-2 solid var(--radio-icon-border-color);
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--radio-btn-foreground-color);
|
||||
}
|
||||
.title-name {
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
color: var(--radio-btn-foreground-color);
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
stroke: var(--radio-btn-foreground-color-selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checked {
|
||||
--radio-icon-border-color: var(--radio-btn-border-color-selected);
|
||||
|
||||
background-color: var(--radio-btn-background-color-selected);
|
||||
svg {
|
||||
stroke: var(--radio-btn-foreground-color-selected);
|
||||
}
|
||||
.title-name {
|
||||
color: var(--radio-btn-foreground-color-selected);
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: default;
|
||||
background-color: transparent;
|
||||
border: deprecated.$s-2 solid transparent;
|
||||
svg {
|
||||
stroke: var(--button-foreground-color-disabled);
|
||||
}
|
||||
.title-name {
|
||||
color: var(--button-foreground-color-disabled);
|
||||
}
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border: deprecated.$s-2 solid transparent;
|
||||
svg {
|
||||
stroke: var(--button-foreground-color-disabled);
|
||||
}
|
||||
.title-name {
|
||||
color: var(--button-foreground-color-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,10 +51,6 @@
|
||||
padding: var(--sp-xxl) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
// We need to use the the deprecated z-index so it won't clash with the dashboard
|
||||
// onboarding modals
|
||||
z-index: deprecated.$z-index-3;
|
||||
}
|
||||
|
||||
.nav-inside {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.notifications.context-notification :refer [context-notification]]
|
||||
@@ -184,11 +184,11 @@
|
||||
:on-click on-click
|
||||
:tab-index "0"}
|
||||
[:span (tr "labels.add-custom-font")]
|
||||
[:& file-uploader {:input-id "font-upload"
|
||||
:accept accept-font-types
|
||||
:multi true
|
||||
:ref input-ref
|
||||
:on-selected on-selected}]]
|
||||
[:> file-uploader* {:input-id "font-upload"
|
||||
:accept accept-font-types
|
||||
:multi true
|
||||
:ref input-ref
|
||||
:on-selected on-selected}]]
|
||||
|
||||
(when-let [url cf/terms-of-service-uri]
|
||||
[:& context-notification {:content (tr "dashboard.fonts.hero-text2" url)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.errors :as errors]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.notifications.context-notification :refer [context-notification]]
|
||||
@@ -58,10 +58,10 @@
|
||||
[{:keys [project-id on-finish-import]} external-ref]
|
||||
(let [on-file-selected (use-import-file project-id on-finish-import)]
|
||||
[:form.import-file {:aria-hidden "true"}
|
||||
[:& file-uploader {:accept ".penpot,.zip"
|
||||
:multi true
|
||||
:ref external-ref
|
||||
:on-selected on-file-selected}]]))
|
||||
[:> file-uploader* {:accept ".penpot,.zip"
|
||||
:multi true
|
||||
:ref external-ref
|
||||
:on-selected on-file-selected}]]))
|
||||
|
||||
(defn- update-entry-name
|
||||
[entries file-id new-name]
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.dashboard.change-owner]
|
||||
[app.main.ui.dashboard.subscription :refer [members-cta*
|
||||
@@ -1315,10 +1315,10 @@
|
||||
[:img {:class (stl/css :team-image)
|
||||
:src (cfg/resolve-team-photo-url team)}]
|
||||
(when can-edit
|
||||
[:& file-uploader {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref finput
|
||||
:on-selected on-file-selected}])]
|
||||
[:> file-uploader* {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref finput
|
||||
:on-selected on-file-selected}])]
|
||||
[:div {:class (stl/css :block-label)}
|
||||
(tr "dashboard.team-info")]
|
||||
[:div {:class (stl/css :block-text)}
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
@@ -97,8 +99,11 @@
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h2 {:class (stl/css :modal-title)} title]
|
||||
[:button {:class (stl/css :modal-close-btn)
|
||||
:on-click cancel-fn} deprecated-icon/close]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :modal-close-btn)
|
||||
:icon i/close
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click cancel-fn}]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
(when (and (string? subtitle) (not= subtitle ""))
|
||||
@@ -124,14 +129,10 @@
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
(when-not (= cancel-label :omit)
|
||||
[:input {:class (stl/css :cancel-button)
|
||||
:type "button"
|
||||
:value cancel-label
|
||||
:on-click cancel-fn}])
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click cancel-fn}
|
||||
cancel-label])
|
||||
|
||||
[:input {:class (stl/css-case :accept-btn true
|
||||
:danger (= accept-style :danger)
|
||||
:primary (= accept-style :primary))
|
||||
:type "button"
|
||||
:value accept-label
|
||||
:on-click accept-fn}]]]]]))
|
||||
[:> button* {:variant (if (= accept-style :danger) "destructive" "primary")
|
||||
:on-click accept-fn}
|
||||
accept-label]]]]]))
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
@extend .modal-close-btn-base;
|
||||
position: absolute;
|
||||
top: var(--sp-s);
|
||||
right: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@@ -53,17 +55,6 @@
|
||||
@extend .modal-action-btns;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@extend .modal-cancel-btn;
|
||||
}
|
||||
|
||||
.accept-btn {
|
||||
@extend .modal-accept-btn;
|
||||
&.danger {
|
||||
@extend .modal-danger-btn;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-scd-msg {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.ds.controls.switch :refer [switch*]]
|
||||
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
|
||||
@@ -63,6 +64,7 @@
|
||||
:Select select*
|
||||
:Switch switch*
|
||||
:Checkbox checkbox*
|
||||
:RadioButtons radio-buttons*
|
||||
:Combobox combobox*
|
||||
:Text text*
|
||||
:TabSwitcher tab-switcher*
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
|
||||
107
frontend/src/app/main/ui/ds/controls/radio_buttons.cljs
Normal file
@@ -0,0 +1,107 @@
|
||||
;; 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.controls.radio-buttons
|
||||
(:require-macros
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon-list]]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]
|
||||
[rumext.v2.util :as mfu]))
|
||||
|
||||
(def ^:private schema:radio-button
|
||||
[:map
|
||||
[:id :string]
|
||||
[:icon {:optional true}
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:label :string]
|
||||
[:value [:or :keyword :string]]
|
||||
[:disabled {:optional true} :boolean]])
|
||||
|
||||
(def ^:private schema:radio-buttons
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:variant {:optional true}
|
||||
[:maybe [:enum "primary" "secondary" "ghost" "destructive" "action"]]]
|
||||
[:extended {:optional true} :boolean]
|
||||
[:name {:optional true} :string]
|
||||
[:selected {:optional true}
|
||||
[:maybe [:or :keyword :string]]]
|
||||
[:allow-empty {:optional true} :boolean]
|
||||
[:options [:vector {:min 1} schema:radio-button]]
|
||||
[:on-change {:optional true} fn?]])
|
||||
|
||||
(mf/defc radio-buttons*
|
||||
{::mf/schema schema:radio-buttons}
|
||||
[{:keys [class variant extended name selected allow-empty options on-change] :rest props}]
|
||||
(let [options (if (array? options)
|
||||
(mfu/bean options)
|
||||
options)
|
||||
type (if allow-empty "checkbox" "radio")
|
||||
variant (d/nilv variant "secondary")
|
||||
|
||||
handle-click
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
label (dom/get-parent-with-data target "label")]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(dom/click label))))
|
||||
|
||||
handle-change
|
||||
(mf/use-fn
|
||||
(mf/deps selected on-change)
|
||||
(fn [event]
|
||||
(let [input (dom/get-target event)
|
||||
value (dom/get-target-val event)]
|
||||
(when (fn? on-change)
|
||||
(on-change value event))
|
||||
(dom/blur! input))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:key (dm/str name "-" selected)
|
||||
:class [class (stl/css-case :wrapper true
|
||||
:extended extended)]})]
|
||||
|
||||
[:> :div props
|
||||
(for [[idx {:keys [id class value label icon disabled]}] (d/enumerate options)]
|
||||
(let [checked? (= selected value)]
|
||||
[:label {:key idx
|
||||
:html-for id
|
||||
:data-label true
|
||||
:data-testid id
|
||||
:class [class (stl/css-case :label true
|
||||
:extended extended)]}
|
||||
|
||||
(if (some? icon)
|
||||
[:> icon-button* {:variant variant
|
||||
:on-click handle-click
|
||||
:aria-pressed checked?
|
||||
:aria-label label
|
||||
:icon icon
|
||||
:disabled disabled}]
|
||||
[:> button* {:variant variant
|
||||
:on-click handle-click
|
||||
:aria-pressed checked?
|
||||
:class (stl/css-case :button true
|
||||
:extended extended)
|
||||
:disabled disabled}
|
||||
label])
|
||||
|
||||
[:input {:id id
|
||||
:class (stl/css :input)
|
||||
:on-change handle-change
|
||||
:type type
|
||||
:name name
|
||||
:disabled disabled
|
||||
:value value
|
||||
:default-checked checked?}]]))]))
|
||||
97
frontend/src/app/main/ui/ds/controls/radio_buttons.mdx
Normal file
@@ -0,0 +1,97 @@
|
||||
{ /* 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 RadioButtons from "./radio_buttons.stories";
|
||||
|
||||
<Meta title="Controls/Radio Buttons" />
|
||||
|
||||
# Radio Buttons
|
||||
|
||||
The `radio-buttons*` component allows users to switch between two or more options that are mutually exclusive.
|
||||
|
||||
## Variants
|
||||
|
||||
Radio buttons with text only. The label will be the text of the button.
|
||||
|
||||
<Canvas of={RadioButtons.Default} />
|
||||
|
||||
```clj
|
||||
[:> radio-buttons* {:selected "left"
|
||||
:on-change handle-change
|
||||
:name "alignment"
|
||||
:extended false
|
||||
:allow-empty false
|
||||
:options [{:id "align-left"
|
||||
:label "Left"
|
||||
:value "left"}
|
||||
{:id "align-center"
|
||||
:label "Center"
|
||||
:value "center"}
|
||||
{:id "align-right"
|
||||
:label "Right"
|
||||
:value "right"}]}]
|
||||
```
|
||||
|
||||
Radio buttons with icons only. In this case, the label will act as the tooltip of each button.
|
||||
|
||||
<Canvas of={RadioButtons.WithIcons} />
|
||||
|
||||
```clj
|
||||
(ns app.main.ui.foo
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]))
|
||||
|
||||
[:> radio-buttons* {:selected "left"
|
||||
:on-change handle-change
|
||||
:name "alignment"
|
||||
:extended false
|
||||
:allow-empty false
|
||||
:options [{:id "align-left"
|
||||
:icon i/text-align-left
|
||||
:label "Left align"
|
||||
:value "left"}
|
||||
{:id "align-center"
|
||||
:icon i/text-align-center
|
||||
:label "Center align"
|
||||
:value "center"}
|
||||
{:id "align-right"
|
||||
:icon i/text-align-right
|
||||
:label "Right align"
|
||||
:value "right"}]}]
|
||||
```
|
||||
|
||||
## Anatomy
|
||||
|
||||
Under the hood, each option is represented by
|
||||
- a button, which is the visible and clickable element. It may be either an icon button or a text button.
|
||||
- a radio input, which is not visible but retains the current state of the option.
|
||||
|
||||
A radio group is defined by giving each of radio buttons in the group the same name. Once a radio group is established,
|
||||
selecting any radio button in that group automatically deselects any currently-selected radio button in the same group.
|
||||
|
||||
The `selected` parameter should be set to the value of the option that is to be active. Otherwise, no option will be selected.
|
||||
|
||||
If the parameter `allow-empty` is enabled, then the component will work with checkboxes instead of radio buttons,
|
||||
and therefore the selected option can be deselected. However, it will still only be possible to select one option.
|
||||
|
||||
The `extended` parameter allows the component to use all the available space from the parent and distribute it equally
|
||||
among all elements.
|
||||
|
||||
Any option can be individually disabled using the `disabled` parameter.
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### When to Use
|
||||
|
||||
- For multiple choice settings that take effect immediately.
|
||||
- In preference panels and configuration screens.
|
||||
|
||||
### When Not to Use
|
||||
|
||||
- For boolean settings (use switch or checkbox instead).
|
||||
- For actions that require confirmation (use buttons instead).
|
||||
- For temporary states that need explicit "Apply" action.
|
||||
40
frontend/src/app/main/ui/ds/controls/radio_buttons.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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/spacing.scss" as *;
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
gap: var(--sp-xs);
|
||||
|
||||
&.extended {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
&.extended {
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
&.extended {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { RadioButtons } = Components;
|
||||
|
||||
const options = [
|
||||
{id: "left", label: "Left", value: "left" },
|
||||
{id: "center", label: "Center", value: "center" },
|
||||
{id: "right", label: "Right", value: "right" },
|
||||
];
|
||||
|
||||
const optionsIcon = [
|
||||
{id: "left", label: "Left align", value: "left", icon: "text-align-left" },
|
||||
{id: "center", label: "Center align", value: "center", icon: "text-align-center" },
|
||||
{id: "right", label: "Right align", value: "right", icon: "text-align-right" },
|
||||
];
|
||||
|
||||
export default {
|
||||
title: "Controls/Radio Buttons",
|
||||
component: RadioButtons,
|
||||
argTypes: {
|
||||
name: {
|
||||
control: { type: "text" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
selected: {
|
||||
control: { type: "select" },
|
||||
options: ["", "left", "center", "right"],
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
extended: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
allowEmpty: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is disabled",
|
||||
},
|
||||
},
|
||||
args: {
|
||||
name: "alignment",
|
||||
selected: "left",
|
||||
extended: false,
|
||||
allowEmpty: false,
|
||||
options: options,
|
||||
disabled: false,
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
exclude: ["options", "on-change"],
|
||||
},
|
||||
},
|
||||
render: ({ ...args }) => <RadioButtons {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const WithIcons = {
|
||||
args: {
|
||||
options: optionsIcon,
|
||||
},
|
||||
};
|
||||
49
frontend/src/app/main/ui/ds/layers/layer_button.cljs
Normal file
@@ -0,0 +1,49 @@
|
||||
;; 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]]))
|
||||
56
frontend/src/app/main/ui/ds/layers/layer_button.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -208,7 +208,7 @@
|
||||
;; FIXME: deprecated, should be refactored in two components and use
|
||||
;; the generic progress reporter
|
||||
|
||||
(mf/defc progress-widget
|
||||
(mf/defc progress-widget*
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
(let [state (mf/deref refs/export)
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.code-block :refer [code-block]]
|
||||
[app.main.ui.components.copy-button :refer [copy-button*]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.hooks.resize :refer [use-resize-hook]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.shapes.text.fontfaces :refer [shapes->fonts]]
|
||||
@@ -260,8 +263,9 @@
|
||||
[:div {:class (stl/css-case :element-options true
|
||||
:viewer-code-block (= :viewer from))}
|
||||
[:div {:class (stl/css :attributes-block)}
|
||||
[:button {:class (stl/css :download-button)
|
||||
:on-click handle-copy-all-code}
|
||||
[:> button* {:variant "secondary"
|
||||
:class (stl/css :download-button)
|
||||
:on-click handle-copy-all-code}
|
||||
"Copy all code"]]
|
||||
|
||||
#_[:div.attributes-block
|
||||
@@ -288,10 +292,10 @@
|
||||
;; :options [{:label "CSS" :value "css"}]}]
|
||||
|
||||
[:div {:class (stl/css :action-btns)}
|
||||
[:button {:class (stl/css :expand-button)
|
||||
:on-click on-expand}
|
||||
deprecated-icon/code]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label "Expand"
|
||||
:on-click on-expand
|
||||
:icon i/code}]
|
||||
[:> copy-button* {:data copy-css-fn
|
||||
:class (stl/css :css-copy-btn)
|
||||
:on-copied on-style-copied}]]]
|
||||
@@ -318,21 +322,21 @@
|
||||
:rotated collapsed-markup?)}
|
||||
deprecated-icon/arrow]]
|
||||
|
||||
[:& radio-buttons {:selected markup-type
|
||||
:on-change set-markup
|
||||
:class (stl/css :code-lang-options)
|
||||
:wide true
|
||||
:name "listing-style"}
|
||||
[:& radio-button {:value "html"
|
||||
:id :html}]
|
||||
[:& radio-button {:value "svg"
|
||||
:id :svg}]]
|
||||
[:> radio-buttons* {:selected markup-type
|
||||
:on-change set-markup
|
||||
:name "listing-style"
|
||||
:options [{:id "html"
|
||||
:label "HTML"
|
||||
:value "html"}
|
||||
{:id "svg"
|
||||
:label "SVG"
|
||||
:value "svg"}]}]
|
||||
|
||||
[:div {:class (stl/css :action-btns)}
|
||||
[:button {:class (stl/css :expand-button)
|
||||
:on-click on-expand}
|
||||
deprecated-icon/code]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label "Expand"
|
||||
:on-click on-expand
|
||||
:icon i/code}]
|
||||
[:> copy-button* {:data copy-html-fn
|
||||
:class (stl/css :html-copy-btn)
|
||||
:on-copied on-markup-copied}]]]
|
||||
|
||||
@@ -17,16 +17,18 @@
|
||||
padding-inline: var(--sp-m);
|
||||
}
|
||||
|
||||
.attributes-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
.viewer-code-block {
|
||||
height: calc(100vh - #{deprecated.$s-108}); // TODO: Fix this hardcoded value
|
||||
}
|
||||
|
||||
.download-button {
|
||||
@extend .button-secondary;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
margin: deprecated.$s-8 0;
|
||||
margin: var(--sp-s) 0;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
@@ -73,7 +75,6 @@
|
||||
gap: deprecated.$s-4;
|
||||
}
|
||||
|
||||
.expand-button,
|
||||
.css-copy-btn,
|
||||
.html-copy-btn {
|
||||
@extend .button-tertiary;
|
||||
@@ -85,9 +86,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.code-lang-options {
|
||||
max-width: deprecated.$s-108;
|
||||
}
|
||||
.code-lang-select {
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
width: deprecated.$s-72;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -110,11 +110,11 @@
|
||||
[:span {:class (stl/css :update-overlay)
|
||||
:on-click on-image-click} (tr "labels.update")]
|
||||
[:img {:src photo}]
|
||||
[:& file-uploader {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref input-ref
|
||||
:on-selected on-file-selected
|
||||
:data-testid "profile-image-input"}]]]))
|
||||
[:> file-uploader* {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref input-ref
|
||||
:on-selected on-file-selected
|
||||
:data-testid "profile-image-input"}]]]))
|
||||
|
||||
;; --- Profile Page
|
||||
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc comments-menu
|
||||
{::mf/props :obj
|
||||
::mf/memo true}
|
||||
(mf/defc comments-menu*
|
||||
{::mf/memo true}
|
||||
[]
|
||||
(let [state (mf/deref refs/comments-local)
|
||||
cmode (:mode state)
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
[app.main.data.viewer.shortcuts :as sc]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.exports.assets :refer [progress-widget]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.exports.assets :refer [progress-widget*]]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.viewer.comments :refer [comments-menu]]
|
||||
[app.main.ui.viewer.comments :refer [comments-menu*]]
|
||||
[app.main.ui.viewer.interactions :refer [flows-menu* interactions-menu*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -33,20 +36,12 @@
|
||||
[]
|
||||
(modal/show! :login-register {}))
|
||||
|
||||
(mf/defc zoom-widget
|
||||
{::mf/memo true
|
||||
::mf/props :obj}
|
||||
[{:keys [zoom
|
||||
on-increase
|
||||
on-decrease
|
||||
on-zoom-reset
|
||||
on-fullscreen
|
||||
on-zoom-fit
|
||||
on-zoom-fill]
|
||||
:as props}]
|
||||
(mf/defc zoom-widget*
|
||||
{::mf/memo true}
|
||||
[{:keys [zoom on-increase on-decrease on-zoom-reset on-fullscreen on-zoom-fit on-zoom-fill]}]
|
||||
(let [open* (mf/use-state false)
|
||||
open? (deref open*)
|
||||
|
||||
(let [open* (mf/use-state false)
|
||||
open? (deref open*)
|
||||
open-dropdown
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
@@ -75,7 +70,7 @@
|
||||
|
||||
[:div {:class (stl/css-case :zoom-widget true
|
||||
:selected open?)
|
||||
:on-click open-dropdown
|
||||
:on-click (if open? close-dropdown open-dropdown)
|
||||
:title (tr "workspace.header.zoom")}
|
||||
[:span {:class (stl/css :label)} (fmt/format-percent zoom)]
|
||||
[:& dropdown {:show open?
|
||||
@@ -83,18 +78,18 @@
|
||||
[:ul {:class (stl/css :dropdown)}
|
||||
[:li {:class (stl/css :basic-zoom-bar)}
|
||||
[:span {:class (stl/css :zoom-btns)}
|
||||
[:button {:class (stl/css :zoom-btn)
|
||||
:on-click on-decrease}
|
||||
[:span {:class (stl/css :zoom-icon)}
|
||||
deprecated-icon/remove-icon]]
|
||||
[:p {:class (stl/css :zoom-text)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "shortcuts.decrease-zoom")
|
||||
:on-click on-decrease
|
||||
:icon i/remove}]
|
||||
[:p {:class (stl/css :zoom-text)}
|
||||
(fmt/format-percent zoom)]
|
||||
[:button {:class (stl/css :zoom-btn)
|
||||
:on-click on-increase}
|
||||
[:span {:class (stl/css :zoom-icon)}
|
||||
deprecated-icon/add]]]
|
||||
[:button {:class (stl/css :reset-btn)
|
||||
:on-click on-zoom-reset}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "shortcuts.increase-zoom")
|
||||
:on-click on-increase
|
||||
:icon i/add}]]
|
||||
[:> button* {:variant "ghost"
|
||||
:on-click on-zoom-reset}
|
||||
(tr "workspace.header.reset-zoom")]]
|
||||
|
||||
[:li {:class (stl/css :zoom-option)
|
||||
@@ -119,7 +114,7 @@
|
||||
[:span {:class (stl/css :shortcut-key)
|
||||
:key (dm/str "zoom-fullscreen-" sc)} sc])]]]]]))
|
||||
|
||||
(mf/defc header-options
|
||||
(mf/defc header-options*
|
||||
[{:keys [section zoom page file index permissions interactions-mode share]}]
|
||||
(let [fullscreen? (mf/deref fullscreen-ref)
|
||||
|
||||
@@ -159,6 +154,7 @@
|
||||
handle-zoom-fit
|
||||
(mf/use-fn
|
||||
#(st/emit! dv/zoom-to-fit))]
|
||||
|
||||
(mf/with-effect [permissions share]
|
||||
(when (and
|
||||
(:in-team permissions)
|
||||
@@ -167,7 +163,7 @@
|
||||
(open-share-dialog)))
|
||||
|
||||
[:div {:class (stl/css :options-zone)}
|
||||
[:& progress-widget]
|
||||
[:> progress-widget*]
|
||||
|
||||
(case section
|
||||
:interactions [:*
|
||||
@@ -175,40 +171,41 @@
|
||||
[:> flows-menu* {:page page :index index}])
|
||||
[:> interactions-menu*
|
||||
{:interactions-mode interactions-mode}]]
|
||||
:comments [:& comments-menu]
|
||||
:comments [:> comments-menu*]
|
||||
[:div {:class (stl/css :view-options)}])
|
||||
|
||||
[:& zoom-widget
|
||||
{:zoom zoom
|
||||
:on-increase handle-increase
|
||||
:on-decrease handle-decrease
|
||||
:on-zoom-reset handle-zoom-reset
|
||||
:on-zoom-fill handle-zoom-fill
|
||||
:on-zoom-fit handle-zoom-fit
|
||||
:on-fullscreen toggle-fullscreen}]
|
||||
[:> zoom-widget* {:zoom zoom
|
||||
:on-increase handle-increase
|
||||
:on-decrease handle-decrease
|
||||
:on-zoom-reset handle-zoom-reset
|
||||
:on-zoom-fill handle-zoom-fill
|
||||
:on-zoom-fit handle-zoom-fit
|
||||
:on-fullscreen toggle-fullscreen}]
|
||||
|
||||
(when (:in-team permissions)
|
||||
[:span {:on-click go-to-workspace
|
||||
:class (stl/css :edit-btn)}
|
||||
deprecated-icon/curve])
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "viewer.header.edit-in-workspace")
|
||||
:on-click go-to-workspace
|
||||
:icon i/curve}])
|
||||
|
||||
[:span {:title (tr "viewer.header.fullscreen")
|
||||
:class (stl/css-case :fullscreen-btn true
|
||||
:selected fullscreen?)
|
||||
:on-click toggle-fullscreen}
|
||||
deprecated-icon/expand]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed fullscreen?
|
||||
:aria-label (tr "viewer.header.fullscreen")
|
||||
:on-click toggle-fullscreen
|
||||
:icon i/expand}]
|
||||
|
||||
(when (:in-team permissions)
|
||||
[:button {:on-click open-share-dialog
|
||||
:class (stl/css :share-btn)}
|
||||
[:> button* {:variant "primary"
|
||||
:class (stl/css :share-btn)
|
||||
:on-click open-share-dialog}
|
||||
(tr "labels.share")])
|
||||
|
||||
(when-not (:is-logged permissions)
|
||||
[:span {:on-click open-login-dialog
|
||||
:class (stl/css :go-log-btn)} (tr "labels.log-or-sign")])]))
|
||||
|
||||
(mf/defc header-sitemap
|
||||
[{:keys [project file page frame toggle-thumbnails] :as props}]
|
||||
(mf/defc header-sitemap*
|
||||
[{:keys [project file page frame toggle-thumbnails]}]
|
||||
(let [project-name (:name project)
|
||||
file-name (:name file)
|
||||
page-name (:name page)
|
||||
@@ -317,44 +314,44 @@
|
||||
:pointer-events (when-not (:in-team permissions) "none")}}
|
||||
penpot-logo-icon]
|
||||
|
||||
[:& header-sitemap {:project project
|
||||
:file file
|
||||
:page page
|
||||
:frame frame
|
||||
:toggle-thumbnails toggle-thumbnails
|
||||
:index index}]]
|
||||
[:> header-sitemap* {:project project
|
||||
:file file
|
||||
:page page
|
||||
:frame frame
|
||||
:toggle-thumbnails toggle-thumbnails
|
||||
:index index}]]
|
||||
|
||||
[:div {:class (stl/css :mode-zone)}
|
||||
[:button {:on-click navigate
|
||||
:data-value "interactions"
|
||||
:class (stl/css-case :mode-zone-btn true
|
||||
:selected (= section :interactions))
|
||||
:title (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))}
|
||||
deprecated-icon/play]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (= section :interactions)
|
||||
:aria-label (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))
|
||||
:data-value "interactions"
|
||||
:on-click navigate
|
||||
:icon i/play}]
|
||||
|
||||
(when (or (:in-team permissions)
|
||||
(= (:who-comment permissions) "all"))
|
||||
[:button {:on-click navigate
|
||||
:data-value "comments"
|
||||
:class (stl/css-case :mode-zone-btn true
|
||||
:selected (= section :comments))
|
||||
:title (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))}
|
||||
deprecated-icon/comments])
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (= section :comments)
|
||||
:aria-label (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))
|
||||
:data-value "comments"
|
||||
:on-click navigate
|
||||
:icon i/comments}])
|
||||
|
||||
(when (or (:in-team permissions)
|
||||
(and (= (:type permissions) :share-link)
|
||||
(= (:who-inspect permissions) "all")))
|
||||
[:button {:on-click go-to-inspect
|
||||
:class (stl/css-case :mode-zone-btn true
|
||||
:selected (= section :inspect))
|
||||
:title (tr "viewer.header.inspect-section" (sc/get-tooltip :open-inspect))}
|
||||
deprecated-icon/code])]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (= section :inspect)
|
||||
:aria-label (tr "viewer.header.inspect-section" (sc/get-tooltip :open-inspect))
|
||||
:on-click go-to-inspect
|
||||
:icon i/code}])]
|
||||
|
||||
[:& header-options {:section section
|
||||
:permissions permissions
|
||||
:page page
|
||||
:file file
|
||||
:index index
|
||||
:zoom zoom
|
||||
:interactions-mode interactions-mode
|
||||
:share share}]]))
|
||||
[:> header-options* {:section section
|
||||
:permissions permissions
|
||||
:page page
|
||||
:file file
|
||||
:index index
|
||||
:zoom zoom
|
||||
:interactions-mode interactions-mode
|
||||
:share share}]]))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr deprecated.$s-92 1fr;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: deprecated.$s-48;
|
||||
@@ -130,23 +130,9 @@
|
||||
|
||||
// SECTION BUTTONS
|
||||
.mode-zone {
|
||||
@include deprecated.flexRow;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mode-zone-btn {
|
||||
@extend .button-tertiary;
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
padding: 0;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
@extend .button-icon-selected;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
// OPTION AREA
|
||||
@@ -165,33 +151,8 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fullscreen-btn {
|
||||
@extend .button-tertiary;
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
@extend .button-primary;
|
||||
height: deprecated.$s-32;
|
||||
min-width: deprecated.$s-72;
|
||||
margin-left: deprecated.$s-4;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
@extend .button-tertiary;
|
||||
@include deprecated.flexCenter;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
margin-left: var(--sp-xs);
|
||||
}
|
||||
|
||||
.go-log-btn {
|
||||
@@ -245,43 +206,15 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-8;
|
||||
.zoom-icon {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-24;
|
||||
height: deprecated.$s-32;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.zoom-icon svg {
|
||||
stroke: var(--button-tertiary-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-text {
|
||||
@include deprecated.flexCenter;
|
||||
height: 100%;
|
||||
min-width: deprecated.$s-64;
|
||||
min-width: deprecated.$s-48;
|
||||
padding: 0;
|
||||
margin: 0 deprecated.$s-2;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
@extend .button-tertiary;
|
||||
color: var(--button-tertiary-foreground-color-hover);
|
||||
height: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
.zoom-option {
|
||||
@extend .menu-item-base;
|
||||
.shortcuts {
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.clipboard :as clipboard]
|
||||
[app.util.dom :as dom]
|
||||
@@ -171,10 +174,11 @@
|
||||
[:div {:class (stl/css :share-link-header)}
|
||||
[:h2 {:class (stl/css :share-link-title)}
|
||||
(tr "common.share-link.title")]
|
||||
[:button {:class (stl/css :modal-close-button)
|
||||
:on-click on-close
|
||||
:title (tr "labels.close")}
|
||||
deprecated-icon/close]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :modal-close-button)
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close
|
||||
:icon i/close}]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :share-link-section)}
|
||||
(when (and (not confirm?) (some? current-link))
|
||||
@@ -185,10 +189,10 @@
|
||||
:placeholder (tr "common.share-link.placeholder")
|
||||
:read-only true}]
|
||||
|
||||
[:button {:class (stl/css :copy-button)
|
||||
:title (tr "viewer.header.share.copy-link")
|
||||
:on-click copy-link}
|
||||
deprecated-icon/clipboard]])
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "viewer.header.share.copy-link")
|
||||
:on-click copy-link
|
||||
:icon i/clipboard}]])
|
||||
|
||||
[:div {:class (stl/css :hint-wrapper)}
|
||||
(when (not ^boolean confirm?)
|
||||
@@ -199,28 +203,22 @@
|
||||
[:div {:class (stl/css :description)}
|
||||
(tr "common.share-link.confirm-deletion-link-description")]
|
||||
[:div {:class (stl/css :actions)}
|
||||
[:input {:type "button"
|
||||
:class (stl/css :button-cancel)
|
||||
:on-click #(reset! confirm* false)
|
||||
:value (tr "labels.cancel")}]
|
||||
[:input {:type "button"
|
||||
:class (stl/css :button-danger)
|
||||
:on-click delete-link
|
||||
:value (tr "common.share-link.destroy-link")}]]]
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click #(reset! confirm* false)}
|
||||
(tr "labels.cancel")]
|
||||
[:> button* {:variant "destructive"
|
||||
:on-click delete-link}
|
||||
(tr "common.share-link.destroy-link")]]]
|
||||
|
||||
(some? current-link)
|
||||
[:input
|
||||
{:type "button"
|
||||
:class (stl/css :button-danger)
|
||||
:on-click try-delete-link
|
||||
:value (tr "common.share-link.destroy-link")}]
|
||||
[:> button* {:variant "destructive"
|
||||
:on-click try-delete-link}
|
||||
(tr "common.share-link.destroy-link")]
|
||||
|
||||
:else
|
||||
[:input
|
||||
{:type "button"
|
||||
:class (stl/css :button-active)
|
||||
:on-click create-link
|
||||
:value (tr "common.share-link.get-link")}])]]
|
||||
[:> button* {:variant "primary"
|
||||
:on-click create-link}
|
||||
(tr "common.share-link.get-link")])]]
|
||||
|
||||
|
||||
(when (not ^boolean confirm?)
|
||||
@@ -305,6 +303,7 @@
|
||||
:options [{:value "team" :label (tr "common.share-link.team-members")}
|
||||
{:value "all" :label (tr "common.share-link.all-users")}]
|
||||
:on-change on-comment-change}]]]
|
||||
|
||||
[:div {:class (stl/css :inspect-mode)}
|
||||
[:div {:class (stl/css :subtitle)}
|
||||
(tr "common.share-link.permissions-can-inspect")]
|
||||
@@ -315,6 +314,3 @@
|
||||
:options [{:value "team" :label (tr "common.share-link.team-members")}
|
||||
{:value "all" :label (tr "common.share-link.all-users")}]
|
||||
:on-change on-inspect-change}]]]])])]]]))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
}
|
||||
|
||||
.modal-close-button {
|
||||
@extend .modal-close-btn-base;
|
||||
position: absolute;
|
||||
top: var(--sp-s);
|
||||
right: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
@@ -74,18 +76,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
@extend .button-secondary;
|
||||
@include deprecated.flexRow;
|
||||
gap: deprecated.$s-8;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
@include deprecated.bodySmallTypography;
|
||||
color: var(--modal-text-foreground-color);
|
||||
@@ -97,18 +87,6 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.button-active {
|
||||
@extend .modal-accept-btn;
|
||||
}
|
||||
|
||||
.button-cancel {
|
||||
@extend .modal-cancel-btn;
|
||||
}
|
||||
|
||||
.button-danger {
|
||||
@extend .modal-danger-btn;
|
||||
}
|
||||
|
||||
.permissions-section {
|
||||
@include deprecated.flexColumn;
|
||||
gap: deprecated.$s-8;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
[app.main.ui.workspace.tokens.import]
|
||||
[app.main.ui.workspace.tokens.import.modal]
|
||||
[app.main.ui.workspace.tokens.management.forms.modals]
|
||||
[app.main.ui.workspace.tokens.remapping-modal]
|
||||
[app.main.ui.workspace.tokens.settings]
|
||||
[app.main.ui.workspace.tokens.themes.create-modal]
|
||||
[app.main.ui.workspace.viewport :refer [viewport*]]
|
||||
|
||||
@@ -25,10 +25,11 @@
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-buttons radio-button]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
@@ -415,24 +416,25 @@
|
||||
:on-change handle-change-mode}]])
|
||||
|
||||
(when (and (= origin :sidebar) show-tokens? token-color)
|
||||
[:& radio-buttons {:selected color-style
|
||||
:on-change toggle-token-color
|
||||
:name "color-style"}
|
||||
[:& radio-button {:icon i/swatches
|
||||
:value :direct-color
|
||||
:title (tr "labels.color")
|
||||
:id "opt-color"}]
|
||||
[:& radio-button {:icon i/tokens
|
||||
:value :token-color
|
||||
:title (tr "workspace.colorpicker.color-tokens")
|
||||
:id "opt-token-color"}]])]
|
||||
[:> radio-buttons* {:selected color-style
|
||||
:on-change toggle-token-color
|
||||
:name "color-style"
|
||||
:options [{:id "swap-opt-list"
|
||||
:icon i/swatches
|
||||
:label (tr "labels.color")
|
||||
:value :direct-color}
|
||||
{:id "swap-opt-grid"
|
||||
:icon i/tokens
|
||||
:label (tr "workspace.colorpicker.color-tokens")
|
||||
:value :token-color}]}])]
|
||||
|
||||
(when (and (not= selected-mode :image)
|
||||
(= color-style :direct-color))
|
||||
[:button {:class (stl/css-case :picker-btn true
|
||||
:selected picking-color?)
|
||||
:on-click handle-click-picker}
|
||||
deprecated-icon/picker])
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.colorpicker.get-color")
|
||||
:aria-pressed picking-color?
|
||||
:on-click handle-click-picker
|
||||
:icon i/picker}])
|
||||
|
||||
(when (= color-style :token-color)
|
||||
[:div {:class (stl/css :token-color-title)}
|
||||
@@ -483,12 +485,11 @@
|
||||
:aria-label (tr "media.choose-image")
|
||||
:on-click on-fill-image-click}
|
||||
(tr "media.choose-image")
|
||||
[:& file-uploader
|
||||
{:input-id "fill-image-upload"
|
||||
:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref fill-image-ref
|
||||
:on-selected on-fill-image-selected}]]])
|
||||
[:> file-uploader* {:input-id "fill-image-upload"
|
||||
:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref fill-image-ref
|
||||
:on-selected on-fill-image-selected}]]])
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :colorpicker-tabs)}
|
||||
|
||||
@@ -46,52 +46,6 @@
|
||||
width: px2rem(68);
|
||||
}
|
||||
|
||||
// TODO: change to DS button component
|
||||
.picker-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
border-radius: $br-8;
|
||||
background-color: transparent;
|
||||
border: $b-1 solid transparent;
|
||||
height: var(--sp-xl);
|
||||
width: var(--sp-xl);
|
||||
border-radius: $br-4;
|
||||
padding: 0;
|
||||
margin-top: var(--sp-xs);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--button-tertiary-foreground-color-rest);
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
stroke: var(--button-tertiary-foreground-color-focus);
|
||||
}
|
||||
}
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
svg {
|
||||
stroke: var(--button-secondary-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
outline: none;
|
||||
border: $b-1 solid transparent;
|
||||
svg {
|
||||
stroke: var(--button-tertiary-foreground-color-active);
|
||||
}
|
||||
}
|
||||
&.selected {
|
||||
svg {
|
||||
stroke: var(--button-tertiary-foreground-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
(when path-set
|
||||
(ptk/data-event :expand-token-sets {:paths path-set}))
|
||||
(dwtl/set-selected-token-set-id id)
|
||||
(dwtl/set-token-type-section-open :color true)
|
||||
(let [{:keys [modal title]} (get dwta/token-properties :color)
|
||||
window-size (dom/get-window-size)
|
||||
left-sidebar (dom/get-element "left-sidebar-aside")
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
@@ -44,12 +45,6 @@
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private close-icon
|
||||
(deprecated-icon/icon-xref :close (stl/css :close-icon)))
|
||||
|
||||
(def ^:private add-icon
|
||||
(deprecated-icon/icon-xref :add (stl/css :add-icon)))
|
||||
|
||||
(defn- get-library-summary
|
||||
"Given a library data return a summary representation of this library"
|
||||
[data]
|
||||
@@ -168,12 +163,10 @@
|
||||
[:div {:class (stl/css :sample-library-item)
|
||||
:key (dm/str id)}
|
||||
[:div {:class (stl/css :sample-library-item-name)} (:name library)]
|
||||
[:input {:class (stl/css-case :sample-library-button true
|
||||
:sample-library-add (nil? importing?)
|
||||
:sample-library-adding (some? importing?))
|
||||
:type "button"
|
||||
:value (if (= importing? id) (tr "labels.adding") (tr "labels.add"))
|
||||
:on-click import-library}]]))
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click import-library
|
||||
:disabled (some? importing?)}
|
||||
(if (= importing? id) (tr "labels.adding") (tr "labels.add"))]]))
|
||||
|
||||
(defn- empty-library?
|
||||
"Check if currentt library summary has elements or not"
|
||||
@@ -322,14 +315,12 @@
|
||||
[:> library-description* {:summary summary}]]]
|
||||
|
||||
(if ^boolean is-shared
|
||||
[:input {:class (stl/css :item-unpublish)
|
||||
:type "button"
|
||||
:value (tr "common.unpublish")
|
||||
:on-click unpublish}]
|
||||
[:input {:class (stl/css :item-publish)
|
||||
:type "button"
|
||||
:value (tr "common.publish")
|
||||
:on-click publish}])]
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click unpublish}
|
||||
(tr "common.unpublish")]
|
||||
[:> button* {:variant "primary"
|
||||
:on-click publish}
|
||||
(tr "common.publish")])]
|
||||
|
||||
(for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries]
|
||||
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)]
|
||||
@@ -377,12 +368,11 @@
|
||||
(let [summary (-> (:library-summary library)
|
||||
(adapt-backend-summary))]
|
||||
[:> library-description* {:summary summary}])]]
|
||||
|
||||
[:button {:class (stl/css :item-button-shared)
|
||||
:data-library-id (dm/str id)
|
||||
:title (tr "workspace.libraries.shared-library-btn")
|
||||
:on-click link-library}
|
||||
add-icon]])]
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:aria-label (tr "workspace.libraries.shared-library-btn")
|
||||
:icon i/add
|
||||
:data-library-id (dm/str id)
|
||||
:on-click link-library}]])]
|
||||
|
||||
(when (empty? shared-libraries)
|
||||
[:div {:class (stl/css :section-list-empty)}
|
||||
@@ -647,11 +637,13 @@
|
||||
:on-click close-dialog-outside
|
||||
:data-testid "libraries-modal"}
|
||||
[:div {:class (stl/css :modal-dialog)}
|
||||
[:button {:class (stl/css :close-btn)
|
||||
:on-click close-dialog
|
||||
:aria-label (tr "labels.close")
|
||||
:data-testid "close-libraries"}
|
||||
close-icon]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:class (stl/css :close-btn)
|
||||
:icon i/close
|
||||
:aria-label (tr "labels.close")
|
||||
:data-testid "close-libraries"
|
||||
:on-click close-dialog}]
|
||||
|
||||
[:div {:class (stl/css :modal-title)}
|
||||
(tr "workspace.libraries.libraries")]
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
background-color: var(--modal-background-color);
|
||||
border: $b-2 solid var(--modal-border-color);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-rows: 0 auto 1fr;
|
||||
min-width: $sz-364;
|
||||
min-height: $sz-192;
|
||||
height: $sz-520;
|
||||
@@ -42,21 +42,10 @@
|
||||
max-width: $sz-712;
|
||||
}
|
||||
|
||||
// TODO: Remove this extended creating modal component
|
||||
.close-btn {
|
||||
@extend .modal-close-btn-base;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: $sz-16;
|
||||
width: $sz-16;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke-width: $b-1;
|
||||
stroke: var(--icon-foreground);
|
||||
position: absolute;
|
||||
top: var(--sp-s);
|
||||
right: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@@ -120,46 +109,6 @@
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.item-publish,
|
||||
.item-unpublish {
|
||||
// TODO: remove this extended by using DS button component
|
||||
@extend .button-primary;
|
||||
@include t.use-typography("headline-small");
|
||||
height: $sz-32;
|
||||
min-width: px2rem(92);
|
||||
padding: var(--sp-s) var(--sp-xxl);
|
||||
margin: 0;
|
||||
border-radius: $br-8;
|
||||
}
|
||||
|
||||
.item-unpublish {
|
||||
// TODO: remove this extended by using DS button component
|
||||
@extend .button-secondary;
|
||||
}
|
||||
|
||||
.item-button,
|
||||
.item-button-shared {
|
||||
// TODO: remove this extended by using DS button component
|
||||
@extend .button-secondary;
|
||||
height: $sz-32;
|
||||
width: $sz-32;
|
||||
margin-inline-start: var(--sp-xxs);
|
||||
padding: var(--sp-s);
|
||||
}
|
||||
|
||||
.detach-icon,
|
||||
.add-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: $sz-16;
|
||||
width: $sz-16;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
stroke-width: $b-1;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
.section-list-shared {
|
||||
max-height: px2rem(272);
|
||||
}
|
||||
@@ -170,26 +119,6 @@
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: px2rem(20);
|
||||
padding: 0 0 0 var(--sp-s);
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
height: px2rem(12);
|
||||
width: px2rem(12);
|
||||
stroke-width: 1.33px;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
// empty state
|
||||
.section-list-empty {
|
||||
display: grid;
|
||||
@@ -428,24 +357,3 @@
|
||||
text-overflow: ellipsis;
|
||||
max-width: px2rem(232);
|
||||
}
|
||||
|
||||
// TODO: Remove this extended using a DS component
|
||||
.sample-library-add {
|
||||
@extend .button-secondary;
|
||||
}
|
||||
|
||||
// TODO: Remove this extended using a DS component
|
||||
.sample-library-adding {
|
||||
@extend .button-disabled;
|
||||
}
|
||||
|
||||
.sample-library-button {
|
||||
@include t.use-typography("headline-small");
|
||||
height: $sz-32;
|
||||
width: px2rem(80);
|
||||
margin: 0;
|
||||
border-radius: $br-8;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@@ -853,8 +853,9 @@
|
||||
|
||||
[:*
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed show-menu?
|
||||
:aria-label (tr "shortcut-subsection.main-menu")
|
||||
:on-click open-menu
|
||||
:on-click (if show-menu? close-all-menus open-menu)
|
||||
:icon i/menu}]
|
||||
|
||||
[:> dropdown-menu* {:show show-menu?
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[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.hooks :as h]
|
||||
[app.main.ui.hooks.resize :as r]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.color-palette :refer [color-palette*]]
|
||||
[app.main.ui.workspace.color-palette-ctx-menu :refer [color-palette-ctx-menu*]]
|
||||
[app.main.ui.workspace.text-palette :refer [text-palette]]
|
||||
@@ -178,27 +179,27 @@
|
||||
[:ul {:class (dm/str size-classname " " (stl/css-case :palette-btn-list true
|
||||
:hidden-bts hide-palettes?))}
|
||||
[:li {:class (stl/css :palette-item)}
|
||||
[:button {:title (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
|
||||
:aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
|
||||
:class (stl/css-case :palette-btn true
|
||||
:selected color-palette?)
|
||||
:on-click on-select-color-palette}
|
||||
deprecated-icon/drop-icon]]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (some? color-palette?)
|
||||
:aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
|
||||
:on-click on-select-color-palette
|
||||
:icon i/drop}]]
|
||||
[:li {:class (stl/css :palette-item)}
|
||||
[:button {:title (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
|
||||
:aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
|
||||
:class (stl/css-case :palette-btn true
|
||||
:selected text-palette?)
|
||||
:on-click on-select-text-palette}
|
||||
deprecated-icon/text-palette]]]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (some? text-palette?)
|
||||
:aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
|
||||
:on-click on-select-text-palette
|
||||
:icon i/text-palette}]]]
|
||||
|
||||
(if any-palette?
|
||||
[:*
|
||||
[:button {:class (stl/css :palette-actions)
|
||||
:on-click #(swap! state* update :show-menu not)}
|
||||
deprecated-icon/menu]
|
||||
[:div {:class (stl/css :menu-btn)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed show-menu?
|
||||
:aria-label (tr "labels.options")
|
||||
:on-click #(swap! state* update :show-menu not)
|
||||
:icon i/menu}]]
|
||||
|
||||
[:div {:class (stl/css :palette)
|
||||
:ref container}
|
||||
(when text-palette?
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
&.wide {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resize-area {
|
||||
grid-area: resize;
|
||||
height: deprecated.$s-8;
|
||||
@@ -72,49 +71,22 @@
|
||||
&.small-palette {
|
||||
display: flex;
|
||||
}
|
||||
.palette-item {
|
||||
@include deprecated.flexCenter;
|
||||
border-radius: deprecated.$br-8;
|
||||
opacity: deprecated.$op-10;
|
||||
transition: opacity 1s ease;
|
||||
.palette-btn {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-clip: padding-box;
|
||||
padding: 0;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.palette-actions {
|
||||
@extend .button-tertiary;
|
||||
grid-area: actions;
|
||||
height: calc(var(--height) - deprecated.$s-16);
|
||||
width: deprecated.$s-32;
|
||||
padding: 0;
|
||||
margin-left: deprecated.$s-4;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--palette-background-color);
|
||||
z-index: deprecated.$z-index-2;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
.palette {
|
||||
grid-area: palette;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.palette-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: var(--sp-s);
|
||||
}
|
||||
|
||||
.handler {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
:style {:background-color color}
|
||||
:src (cfg/resolve-profile-photo-url profile)}]]))
|
||||
|
||||
(mf/defc active-sessions
|
||||
(mf/defc active-sessions*
|
||||
{::mf/memo true}
|
||||
[]
|
||||
(let [profiles (mf/deref refs/profiles)
|
||||
|
||||
@@ -20,23 +20,19 @@
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.dashboard.team]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.exports.assets :refer [progress-widget]]
|
||||
[app.main.ui.exports.assets :refer [progress-widget*]]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.presence :refer [active-sessions]]
|
||||
[app.main.ui.workspace.presence :refer [active-sessions*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ref:persistence-status
|
||||
(l/derived :status refs/persistence))
|
||||
|
||||
;; --- Zoom Widget
|
||||
|
||||
(mf/defc zoom-widget-workspace
|
||||
(mf/defc zoom-widget-workspace*
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [zoom on-increase on-decrease on-zoom-reset on-zoom-fit on-zoom-selected]}]
|
||||
@@ -72,11 +68,12 @@
|
||||
zoom (fmt/format-percent zoom {:precision 0})]
|
||||
|
||||
[:*
|
||||
[:div {:on-click open-dropdown
|
||||
[:div {:on-click (if open? close-dropdown open-dropdown)
|
||||
:class (stl/css-case :zoom-widget true
|
||||
:selected open?)
|
||||
:title (tr "workspace.header.zoom")}
|
||||
[:span {:class (stl/css :label)} zoom]]
|
||||
|
||||
[:& dropdown {:show open? :on-close close-dropdown}
|
||||
[:ul {:class (stl/css :dropdown)}
|
||||
[:li {:class (stl/css :basic-zoom-bar)}
|
||||
@@ -90,9 +87,10 @@
|
||||
:aria-label (tr "shortcuts.increase-zoom")
|
||||
:on-click on-increase
|
||||
:icon i/add}]]
|
||||
[:button {:class (stl/css :reset-btn)
|
||||
:on-click on-zoom-reset}
|
||||
[:> button* {:variant "ghost"
|
||||
:on-click on-zoom-reset}
|
||||
(tr "workspace.header.reset-zoom")]]
|
||||
|
||||
[:li {:class (stl/css :zoom-option)
|
||||
:on-click on-zoom-fit}
|
||||
(tr "workspace.header.zoom-fit-all")
|
||||
@@ -100,6 +98,7 @@
|
||||
(for [sc (scd/split-sc (sc/get-tooltip :fit-all))]
|
||||
[:span {:class (stl/css :shortcut-key)
|
||||
:key (str "zoom-fit-" sc)} sc])]]
|
||||
|
||||
[:li {:class (stl/css :zoom-option)
|
||||
:on-click on-zoom-selected}
|
||||
(tr "workspace.header.zoom-selected")
|
||||
@@ -198,51 +197,43 @@
|
||||
|
||||
[:div {:class (stl/css :workspace-header-right)}
|
||||
[:div {:class (stl/css :users-section)}
|
||||
[:& active-sessions]]
|
||||
[:> active-sessions*]]
|
||||
|
||||
[:& progress-widget]
|
||||
[:> progress-widget*]
|
||||
|
||||
[:div {:class (stl/css :separator)}]
|
||||
|
||||
[:div {:class (stl/css :zoom-section)}
|
||||
[:& zoom-widget-workspace
|
||||
{:zoom zoom
|
||||
:on-increase on-increase
|
||||
:on-decrease on-decrease
|
||||
:on-zoom-reset on-zoom-reset
|
||||
:on-zoom-fit on-zoom-fit
|
||||
:on-zoom-selected on-zoom-selected}]]
|
||||
[:> zoom-widget-workspace* {:zoom zoom
|
||||
:on-increase on-increase
|
||||
:on-decrease on-decrease
|
||||
:on-zoom-reset on-zoom-reset
|
||||
:on-zoom-fit on-zoom-fit
|
||||
:on-zoom-selected on-zoom-selected}]]
|
||||
|
||||
[:div {:class (stl/css :comments-section)}
|
||||
[:button {:title (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
|
||||
:aria-label (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
|
||||
:class (stl/css-case :comments-btn true
|
||||
:selected (= selected-drawtool :comments))
|
||||
:on-click toggle-comments
|
||||
:data-tool "comments"
|
||||
:style {:position "relative"}}
|
||||
deprecated-icon/comments
|
||||
(when ^boolean has-unread-comments?
|
||||
[:div {:class (stl/css :unread)}])]]
|
||||
[:div {:class (stl/css :comments-button-wrapper)}
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (= selected-drawtool :comments)
|
||||
:aria-label (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
|
||||
:on-click toggle-comments
|
||||
:icon i/comments}]
|
||||
(when ^boolean has-unread-comments?
|
||||
[:div {:class (stl/css :unread)}])]
|
||||
|
||||
(when-not ^boolean read-only?
|
||||
[:div {:class (stl/css :history-section)}
|
||||
[:button
|
||||
{:title (tr "workspace.sidebar.history")
|
||||
:aria-label (tr "workspace.sidebar.history")
|
||||
:class (stl/css-case :selected (contains? layout :document-history)
|
||||
:history-button true)
|
||||
:on-click toggle-history}
|
||||
deprecated-icon/history]])
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-pressed (contains? layout :document-history)
|
||||
:aria-label (tr "workspace.sidebar.history")
|
||||
:on-click toggle-history
|
||||
:icon i/history}])
|
||||
|
||||
(when display-share-button?
|
||||
[:a {:class (stl/css :viewer-btn)
|
||||
:title (tr "workspace.header.share")
|
||||
:on-click open-share-dialog}
|
||||
deprecated-icon/share])
|
||||
|
||||
[:a {:class (stl/css :viewer-btn)
|
||||
:title (tr "workspace.header.viewer" (sc/get-tooltip :open-viewer))
|
||||
:on-click nav-to-viewer}
|
||||
deprecated-icon/play]]))
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.header.share")
|
||||
:on-click open-share-dialog
|
||||
:icon i/to-corner}])
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.header.viewer" (sc/get-tooltip :open-viewer))
|
||||
:on-click nav-to-viewer
|
||||
:icon i/play}]]))
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-width: deprecated.$s-256;
|
||||
padding: deprecated.$s-8;
|
||||
gap: deprecated.$s-8;
|
||||
padding: deprecated.$s-8 deprecated.$s-12;
|
||||
gap: deprecated.$s-4;
|
||||
background-color: var(--panel-background-color);
|
||||
}
|
||||
|
||||
@@ -28,19 +28,14 @@
|
||||
}
|
||||
|
||||
.zoom-widget {
|
||||
@include deprecated.buttonStyle;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: deprecated.$s-28;
|
||||
max-width: deprecated.$s-48;
|
||||
width: deprecated.$s-48;
|
||||
border-radius: deprecated.$br-8;
|
||||
height: deprecated.$s-32;
|
||||
|
||||
.label {
|
||||
@include deprecated.bodySmallTypography;
|
||||
height: 100%;
|
||||
padding: deprecated.$s-8 0;
|
||||
color: var(--button-tertiary-foreground-color-rest);
|
||||
}
|
||||
|
||||
@@ -84,13 +79,6 @@
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
@extend .button-tertiary;
|
||||
color: var(--button-tertiary-foreground-color-hover);
|
||||
height: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
.zoom-option {
|
||||
@extend .menu-item-base;
|
||||
|
||||
@@ -113,127 +101,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.comments-btn {
|
||||
@extend .button-tertiary;
|
||||
border-radius: deprecated.$br-8;
|
||||
margin: 0;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
border: none;
|
||||
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--button-tertiary-background-color-selected);
|
||||
|
||||
svg {
|
||||
stroke: var(--button-tertiary-foreground-color-active);
|
||||
}
|
||||
}
|
||||
.comments-button-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.history-button {
|
||||
@extend .button-tertiary;
|
||||
border-radius: deprecated.$br-8;
|
||||
margin: 0;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
border: none;
|
||||
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--button-tertiary-background-color-selected);
|
||||
|
||||
svg {
|
||||
stroke: var(--button-tertiary-foreground-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.persistence-status-widget {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-28;
|
||||
height: deprecated.$s-28;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-24;
|
||||
height: deprecated.$s-24;
|
||||
margin: 0;
|
||||
border-radius: deprecated.$br-circle;
|
||||
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--status-widget-icon-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.pending-status {
|
||||
background-color: var(--status-widget-background-color-warning);
|
||||
}
|
||||
|
||||
.saving-status {
|
||||
background-color: var(--status-widget-background-color-pending);
|
||||
|
||||
svg {
|
||||
animation: spin-animation 1s infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
}
|
||||
|
||||
.saved-status {
|
||||
background-color: var(--status-widget-background-color-success);
|
||||
}
|
||||
|
||||
.error-status {
|
||||
background-color: var(--status-widget-background-color-error);
|
||||
}
|
||||
|
||||
.share-btn,
|
||||
.viewer-btn {
|
||||
@extend .button-tertiary;
|
||||
border-radius: deprecated.$br-8;
|
||||
margin: 0;
|
||||
width: deprecated.$s-28;
|
||||
height: deprecated.$s-28;
|
||||
border: none;
|
||||
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.unread {
|
||||
.comments-button-unread {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.sidebar.assets.file-library :refer [file-library*]]
|
||||
[app.util.dom :as dom]
|
||||
@@ -161,43 +162,40 @@
|
||||
:id "typographies"
|
||||
:handler on-section-filter-change}])]
|
||||
|
||||
[:article {:class (stl/css :assets-bar)}
|
||||
[:article {:class (stl/css :assets-bar)}
|
||||
[:div {:class (stl/css :assets-header)}
|
||||
(when-not ^boolean read-only?
|
||||
(if (and (= num-libs 1) (empty? components))
|
||||
[:button {:class (stl/css :add-library-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
[:> button* {:variant "primary"
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
(tr "workspace.assets.add-library")]
|
||||
|
||||
[:button {:class (stl/css :libraries-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
(tr "workspace.assets.manage-library")]))
|
||||
|
||||
|
||||
[:div {:class (stl/css :search-wrapper)}
|
||||
[:> search-bar* {:on-change on-search-term-change
|
||||
:value term
|
||||
:placeholder (tr "workspace.assets.search")}
|
||||
[:button
|
||||
{:on-click on-open-menu
|
||||
:title (tr "workspace.assets.filter")
|
||||
:class (stl/css-case :section-button true
|
||||
:opened menu-open?)}
|
||||
deprecated-icon/filter-icon]]
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:icon i/filter
|
||||
:class (stl/css :filter-button)
|
||||
:aria-pressed menu-open?
|
||||
:aria-label (tr "workspace.assets.filter")
|
||||
:on-click on-open-menu}]]
|
||||
|
||||
[:> context-menu*
|
||||
{:on-close on-menu-close
|
||||
:selectable true
|
||||
:selected section
|
||||
:show menu-open?
|
||||
:fixed true
|
||||
:min-width true
|
||||
:width size
|
||||
:top 158
|
||||
:left 18
|
||||
:options options}]
|
||||
[:> context-menu* {:on-close on-menu-close
|
||||
:selectable true
|
||||
:selected section
|
||||
:show menu-open?
|
||||
:fixed true
|
||||
:min-width true
|
||||
:width size
|
||||
:top 158
|
||||
:left 18
|
||||
:options options}]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.assets.sort")
|
||||
|
||||
@@ -17,89 +17,14 @@
|
||||
padding-top: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.libraries-button {
|
||||
@extend .button-secondary;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
gap: deprecated.$s-2;
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
margin-bottom: deprecated.$s-4;
|
||||
border-radius: deprecated.$s-8;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-secondary-background-color-hover);
|
||||
color: var(--button-secondary-foreground-color-hover);
|
||||
border: deprecated.$s-1 solid var(--button-secondary-border-color-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--button-secondary-background-color-focus);
|
||||
color: var(--button-secondary-foreground-color-focus);
|
||||
border: deprecated.$s-1 solid var(--button-secondary-border-color-focus);
|
||||
}
|
||||
.assets-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxs);
|
||||
}
|
||||
|
||||
.add-library-button {
|
||||
@extend .button-primary;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
gap: deprecated.$s-2;
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
margin-bottom: deprecated.$s-4;
|
||||
border-radius: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.section-button {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.buttonStyle;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
margin: 0;
|
||||
border: deprecated.$s-1 solid var(--input-border-color-rest);
|
||||
border-radius: deprecated.$br-8 deprecated.$br-2 deprecated.$br-2 deprecated.$br-8;
|
||||
background-color: var(--input-background-color-rest);
|
||||
|
||||
svg {
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
outline: 0;
|
||||
background-color: var(--input-background-color-focus);
|
||||
color: var(--input-foreground-color-focus);
|
||||
|
||||
svg {
|
||||
background-color: var(--input-background-color-focus);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
background-color: var(--input-background-color-hover);
|
||||
|
||||
svg {
|
||||
background-color: var(--input-background-color-hover);
|
||||
stroke: var(--button-foreground-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
outline: 0;
|
||||
background-color: var(--input-background-color-focus);
|
||||
color: var(--input-foreground-color-focus);
|
||||
|
||||
svg {
|
||||
background-color: var(--input-background-color-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.opened {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
.filter-button {
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
}
|
||||
|
||||
.sections-container {
|
||||
@@ -125,10 +50,6 @@
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
.section-btn {
|
||||
@include deprecated.buttonStyle;
|
||||
}
|
||||
|
||||
.assets-header {
|
||||
padding: 0 0 deprecated.$s-24 deprecated.$s-12;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.editable-label :refer [editable-label*]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
@@ -563,27 +563,27 @@
|
||||
[:> cmm/asset-section-block* {:role :title-button}
|
||||
(when ^boolean is-open
|
||||
[:div
|
||||
[:& radio-buttons {:selected (if is-listing-thumbs "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "listing-style"}
|
||||
[:& radio-button {:icon i/view-as-list
|
||||
:value "list"
|
||||
:title (tr "workspace.assets.list-view")
|
||||
:id "opt-list"}]
|
||||
[:& radio-button {:icon i/flex-grid
|
||||
:value "grid"
|
||||
:title (tr "workspace.assets.grid-view")
|
||||
:id "opt-grid"}]]])
|
||||
[:> radio-buttons* {:selected (if is-listing-thumbs "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "listing-style"
|
||||
:options [{:id "opt-list"
|
||||
:icon i/view-as-list
|
||||
:label (tr "workspace.assets.list-view")
|
||||
:value "list"}
|
||||
{:id "opt-grid"
|
||||
:icon i/flex-grid
|
||||
:label (tr "workspace.assets.grid-view")
|
||||
:value "grid"}]}]])
|
||||
|
||||
(when (and (not read-only?) is-local)
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.assets.components.add-component")
|
||||
:on-click add-component
|
||||
:icon i/add}
|
||||
[:& file-uploader {:accept dwm/accept-image-types
|
||||
:multi true
|
||||
:ref input-ref
|
||||
:on-selected on-file-selected}]])]
|
||||
[:> file-uploader* {:accept dwm/accept-image-types
|
||||
:multi true
|
||||
:ref input-ref
|
||||
:on-selected on-file-selected}]])]
|
||||
|
||||
[:> cmm/asset-section-block* {:role :content}
|
||||
(when ^boolean is-open
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.sidebar.assets.groups :as grp]
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
@@ -113,18 +113,17 @@
|
||||
:on-drag-over dom/prevent-default
|
||||
:on-drop on-drop}
|
||||
|
||||
[:& typography-entry
|
||||
{:file-id file-id
|
||||
:typography typography
|
||||
:local? local?
|
||||
:selected? (contains? selected typography-id)
|
||||
:on-click on-asset-click
|
||||
:on-change handle-change
|
||||
:on-context-menu on-context-menu
|
||||
:editing? editing?
|
||||
:renaming? renaming?
|
||||
:focus-name? rename?
|
||||
:external-open* open*}]
|
||||
[:> typography-entry* {:file-id file-id
|
||||
:typography typography
|
||||
:local? local?
|
||||
:selected? (contains? selected typography-id)
|
||||
:on-click on-asset-click
|
||||
:on-change handle-change
|
||||
:on-context-menu on-context-menu
|
||||
:editing? editing?
|
||||
:renaming? renaming?
|
||||
:focus-name? rename?
|
||||
:external-open* open*}]
|
||||
(when ^boolean dragging?
|
||||
[:div {:class (stl/css :dragging)}])]))
|
||||
|
||||
|
||||
@@ -139,30 +139,29 @@
|
||||
:variant-properties variant-properties
|
||||
:variant-error variant-error
|
||||
:component-id (:id component)
|
||||
:is-hidden hidden?}]
|
||||
|
||||
(when (not read-only?)
|
||||
[:div {:class (stl/css-case
|
||||
:element-actions true
|
||||
:is-parent has-shapes?
|
||||
:selected hidden?
|
||||
:selected blocked?)}
|
||||
[:button {:class (stl/css-case
|
||||
:toggle-element true
|
||||
:selected hidden?)
|
||||
:title (if hidden?
|
||||
(tr "workspace.shape.menu.show")
|
||||
(tr "workspace.shape.menu.hide"))
|
||||
:on-click on-toggle-visibility}
|
||||
(if ^boolean hidden? deprecated-icon/hide deprecated-icon/shown)]
|
||||
[:button {:class (stl/css-case
|
||||
:block-element true
|
||||
:selected blocked?)
|
||||
:title (if (:blocked item)
|
||||
(tr "workspace.shape.menu.unlock")
|
||||
(tr "workspace.shape.menu.lock"))
|
||||
:on-click on-toggle-blocking}
|
||||
(if ^boolean blocked? deprecated-icon/lock deprecated-icon/unlock)]])]]
|
||||
:is-hidden hidden?}]]
|
||||
(when (not read-only?)
|
||||
[:div {:class (stl/css-case
|
||||
:element-actions true
|
||||
:is-parent has-shapes?
|
||||
:selected hidden?
|
||||
:selected blocked?)}
|
||||
[:button {:class (stl/css-case
|
||||
:toggle-element true
|
||||
:selected hidden?)
|
||||
:title (if hidden?
|
||||
(tr "workspace.shape.menu.show")
|
||||
(tr "workspace.shape.menu.hide"))
|
||||
:on-click on-toggle-visibility}
|
||||
(if ^boolean hidden? deprecated-icon/hide deprecated-icon/shown)]
|
||||
[:button {:class (stl/css-case
|
||||
:block-element true
|
||||
:selected blocked?)
|
||||
:title (if (:blocked item)
|
||||
(tr "workspace.shape.menu.unlock")
|
||||
(tr "workspace.shape.menu.lock"))
|
||||
:on-click on-toggle-blocking}
|
||||
(if ^boolean blocked? deprecated-icon/lock deprecated-icon/unlock)]])]
|
||||
|
||||
children]))
|
||||
|
||||
|
||||
@@ -4,79 +4,81 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
|
||||
.layer-row {
|
||||
--layer-indentation-size: calc(#{deprecated.$s-4} * 6);
|
||||
--layer-indentation-size: var(--sp-xxl);
|
||||
--layer-background-color: var(--color-background-primary);
|
||||
--layer-foreground-color: inherit;
|
||||
--shadow-color: transparent;
|
||||
box-shadow: px2rem(16) px2rem(0) px2rem(0) px2rem(0) var(--shadow-color);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: var(--layer-row-background-color);
|
||||
border: deprecated.$s-2 solid transparent;
|
||||
inline-size: 100%;
|
||||
background-color: var(--layer-background-color);
|
||||
border: $b-2 solid transparent;
|
||||
color: var(--layer-foreground-color);
|
||||
|
||||
&.highlight,
|
||||
&:hover {
|
||||
--layer-background-color: var(--color-background-secondary);
|
||||
--shadow-color: var(--color-background-secondary);
|
||||
--context-hover-color: var(--layer-row-foreground-color-hover);
|
||||
--context-hover-opacity: deprecated.$op-10;
|
||||
background-color: var(--layer-row-background-color-hover);
|
||||
color: var(--layer-row-foreground-color-hover);
|
||||
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
|
||||
var(--layer-row-background-color-hover);
|
||||
--context-hover-opacity: 1;
|
||||
--layer-foreground-color: var(--layer-row-foreground-color-hover);
|
||||
&.hidden {
|
||||
opacity: deprecated.$op-10;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--layer-row-background-color-selected);
|
||||
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
|
||||
var(--layer-row-background-color-selected);
|
||||
--layer-background-color: var(--color-background-quaternary);
|
||||
--shadow-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
&.selected.highlight,
|
||||
&.selected:hover {
|
||||
background-color: var(--layer-row-background-color-selected);
|
||||
--layer-background-color: var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
.parent-selected & {
|
||||
background-color: var(--layer-child-row-background-color);
|
||||
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
|
||||
var(--layer-child-row-background-color);
|
||||
--layer-background-color: var(--color-background-tertiary);
|
||||
--shadow-color: var(--color-background-tertiary);
|
||||
}
|
||||
|
||||
.parent-selected &.highlight,
|
||||
.parent-selected &:hover {
|
||||
background-color: var(--layer-row-background-color-hover);
|
||||
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
|
||||
var(--layer-row-background-color-hover);
|
||||
--layer-background-color: var(--color-background-secondary);
|
||||
--shadow-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
&.dnd-over-bot {
|
||||
border-bottom: deprecated.$s-2 solid var(--layer-row-foreground-color-hover);
|
||||
border-block-end: $b-2 solid var(--color-accent-primary);
|
||||
}
|
||||
&.dnd-over-top {
|
||||
border-top: deprecated.$s-2 solid var(--layer-row-foreground-color-hover);
|
||||
border-block-start: $b-2 solid var(--color-accent-primary);
|
||||
}
|
||||
&.dnd-over {
|
||||
border: deprecated.$s-2 solid var(--layer-row-foreground-color-hover);
|
||||
border: $b-2 solid var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.element-children {
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
background-color: var(--layer-row-background-color-selected);
|
||||
box-shadow: deprecated.$s-16 deprecated.$s-0 deprecated.$s-0 deprecated.$s-0
|
||||
var(--layer-row-background-color-selected);
|
||||
--layer-background-color: var(--color-background-quaternary);
|
||||
--shadow-color: var(--color-background-quaternary);
|
||||
}
|
||||
.layer-row.type-comp & {
|
||||
color: var(--layer-row-component-foreground-color);
|
||||
--layer-foreground-color: var(--color-accent-secondary);
|
||||
}
|
||||
.layer-row.selected & {
|
||||
background-color: transparent;
|
||||
color: var(--layer-row-foreground-color-selected);
|
||||
--layer-background-color: transparent;
|
||||
--layer-foreground-color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,21 +86,22 @@
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
column-gap: deprecated.$s-4;
|
||||
height: deprecated.$s-32;
|
||||
width: calc(100% - (var(--depth) * var(--layer-indentation-size)));
|
||||
column-gap: var(--sp-xs);
|
||||
block-size: $sz-32;
|
||||
inline-size: calc(100% - (var(--depth) * var(--layer-indentation-size)));
|
||||
cursor: pointer;
|
||||
min-width: px2rem(140);
|
||||
min-inline-size: px2rem(140);
|
||||
&.filtered {
|
||||
width: calc(100% - deprecated.$s-12);
|
||||
inline-size: calc(100% - $sz-12);
|
||||
}
|
||||
}
|
||||
|
||||
.element-actions {
|
||||
display: none;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
position: sticky;
|
||||
inset-inline-end: 0;
|
||||
block-size: $sz-32;
|
||||
|
||||
&.selected {
|
||||
display: flex;
|
||||
@@ -111,95 +114,107 @@
|
||||
|
||||
.button-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
.icon-shape {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.buttonStyle;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
justify-self: flex-end;
|
||||
width: deprecated.$s-16;
|
||||
height: 100%;
|
||||
width: deprecated.$s-24;
|
||||
padding-inline-start: deprecated.$s-4;
|
||||
color: var(--icon-foreground);
|
||||
block-size: 100%;
|
||||
inline-size: $sz-24;
|
||||
padding-inline-start: var(--sp-xs);
|
||||
color: var(--color-foreground-secondary);
|
||||
.layer-row.selected & {
|
||||
color: var(--layer-row-foreground-color-selected);
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
.layer-row.type-comp & {
|
||||
color: var(--layer-row-component-foreground-color);
|
||||
color: var(--color-accent-secondary);
|
||||
}
|
||||
.inverse & {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.layer-row.hidden & {
|
||||
opacity: deprecated.$op-7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
opacity: deprecated.$op-10;
|
||||
opacity: 1;
|
||||
svg {
|
||||
stroke: var(--layer-row-foreground-color-hover);
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
background-color: var(--layer-row-foreground-color);
|
||||
opacity: deprecated.$op-4;
|
||||
width: deprecated.$s-12;
|
||||
height: deprecated.$s-12;
|
||||
border-radius: deprecated.$br-2;
|
||||
background-color: var(--color-foreground-secondary);
|
||||
opacity: 0.4;
|
||||
inline-size: $sz-12;
|
||||
block-size: $sz-12;
|
||||
border-radius: px2rem(2);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: deprecated.$op-1;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.layer-row.type-comp & {
|
||||
background-color: var(--layer-row-component-foreground-color);
|
||||
background-color: var(--color-accent-secondary);
|
||||
}
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
opacity: deprecated.$op-4;
|
||||
background-color: var(--layer-row-foreground-color-hover);
|
||||
opacity: 0.4;
|
||||
background-color: var(--color-accent-primary);
|
||||
}
|
||||
.layer-row.selected & {
|
||||
background-color: var(--layer-row-foreground-color-selected);
|
||||
background-color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-content {
|
||||
@include deprecated.buttonStyle;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: deprecated.$s-24;
|
||||
padding-inline-start: deprecated.$s-8;
|
||||
block-size: 100%;
|
||||
inline-size: $sz-24;
|
||||
padding-inline-start: var(--sp-s);
|
||||
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
block-size: $sz-12;
|
||||
inline-size: $sz-12;
|
||||
stroke-width: 1.33px;
|
||||
stroke: var(--color-foreground-secondary);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: deprecated.$op-7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.layer-row.selected & {
|
||||
stroke: var(--layer-row-foreground-color-selected);
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
.layer-row.type-comp & {
|
||||
stroke: var(--layer-row-component-foreground-color);
|
||||
stroke: var(--color-accent-secondary);
|
||||
}
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
opacity: deprecated.$op-10;
|
||||
stroke: var(--layer-row-foreground-color-hover);
|
||||
opacity: 1;
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.layer-row.selected & {
|
||||
background-color: var(--layer-row-background-color-selected);
|
||||
background-color: var(--color-background-quaternary);
|
||||
}
|
||||
&.inverse svg {
|
||||
transform: rotate(90deg);
|
||||
@@ -208,65 +223,78 @@
|
||||
|
||||
.toggle-element,
|
||||
.block-element {
|
||||
@include deprecated.buttonStyle;
|
||||
@include deprecated.flexCenter;
|
||||
height: 100%;
|
||||
width: deprecated.$s-24;
|
||||
--layer-row-action-btn-background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
block-size: 100%;
|
||||
inline-size: $sz-24;
|
||||
margin: 0;
|
||||
display: none;
|
||||
|
||||
background: var(--layer-row-action-btn-background);
|
||||
padding-inline-end: px2rem(6);
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--icon-foreground);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: transparent;
|
||||
fill: none;
|
||||
block-size: $sz-12;
|
||||
inline-size: $sz-12;
|
||||
stroke-width: 1.33px;
|
||||
stroke: var(--color-foreground-secondary);
|
||||
|
||||
.layer-row.hidden & {
|
||||
opacity: deprecated.$op-7;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.type-comp & {
|
||||
stroke: var(--layer-row-component-foreground-color);
|
||||
stroke: var(--color-accent-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.element-actions.selected & {
|
||||
display: flex;
|
||||
opacity: deprecated.$op-0;
|
||||
|
||||
opacity: 0;
|
||||
&.selected {
|
||||
opacity: deprecated.$op-10;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.layer-row:hover .element-actions.selected & {
|
||||
opacity: deprecated.$op-10;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.layer-row.highlight &,
|
||||
.layer-row:hover & {
|
||||
display: flex;
|
||||
--layer-row-action-btn-background: var(--color-background-secondary);
|
||||
svg {
|
||||
opacity: deprecated.$op-10;
|
||||
stroke: var(--layer-row-foreground-color-hover);
|
||||
opacity: 1;
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
.layer-row.selected & {
|
||||
display: flex;
|
||||
--layer-row-action-btn-background: var(--color-background-quaternary);
|
||||
svg {
|
||||
stroke: var(--layer-row-foreground-color-selected);
|
||||
stroke: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.sticky) {
|
||||
position: sticky;
|
||||
top: deprecated.$s-0;
|
||||
z-index: deprecated.$z-index-4;
|
||||
inset-block-start: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.tab-indentation {
|
||||
display: block;
|
||||
height: deprecated.$s-16;
|
||||
min-width: calc(var(--depth) * var(--layer-indentation-size));
|
||||
block-size: $sz-16;
|
||||
min-inline-size: calc(var(--depth) * var(--layer-indentation-size));
|
||||
}
|
||||
.filtered {
|
||||
min-width: deprecated.$s-12;
|
||||
min-inline-size: $sz-12;
|
||||
}
|
||||
|
||||
@@ -291,13 +291,12 @@
|
||||
:value current-search
|
||||
:on-clear clear-search-text
|
||||
:placeholder (tr "workspace.sidebar.layers.search")}
|
||||
[:button {:on-click on-toggle-filters-click
|
||||
:class (stl/css-case
|
||||
:filter-button true
|
||||
:opened show-menu?
|
||||
:active active?)}
|
||||
[:> icon* {:icon-id i/filter}]]]
|
||||
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:class (stl/css :filter-button)
|
||||
:aria-pressed show-menu?
|
||||
:aria-label (tr "workspace.sidebar.layers.filter")
|
||||
:on-click on-toggle-filters-click
|
||||
:icon i/filter}]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click toggle-search
|
||||
|
||||
@@ -19,39 +19,7 @@
|
||||
padding: 0 deprecated.$s-12 0 deprecated.$s-8;
|
||||
gap: deprecated.$s-4;
|
||||
.filter-button {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.buttonStyle;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
margin: 0;
|
||||
border: deprecated.$s-1 solid var(--color-background-tertiary);
|
||||
border-radius: deprecated.$br-8 deprecated.$br-2 deprecated.$br-2 deprecated.$br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
svg {
|
||||
height: deprecated.$s-16;
|
||||
width: deprecated.$s-16;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
&:focus {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
outline: 0;
|
||||
background-color: var(--input-background-color-active);
|
||||
color: var(--input-foreground-color-active);
|
||||
svg {
|
||||
background-color: var(--input-background-color-active);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
background-color: var(--input-background-color-hover);
|
||||
svg {
|
||||
background-color: var(--input-background-color-hover);
|
||||
stroke: var(--button-foreground-hover);
|
||||
}
|
||||
}
|
||||
&.opened {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +111,7 @@
|
||||
.filters-container {
|
||||
@extend .menu-dropdown;
|
||||
position: absolute;
|
||||
left: deprecated.$s-20;
|
||||
left: deprecated.$s-16;
|
||||
width: deprecated.$s-192;
|
||||
.filter-menu-item {
|
||||
@include deprecated.bodySmallTypography;
|
||||
@@ -212,6 +180,8 @@
|
||||
overflow-y: overlay;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.element-list {
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
@@ -95,15 +95,15 @@
|
||||
(when preset-match
|
||||
[:span {:class (stl/css :check-icon)} deprecated-icon/tick])])))]]]
|
||||
|
||||
[:& radio-buttons {:selected (or (d/name orientation) "")
|
||||
:on-change on-orientation-change
|
||||
:name "frame-orientation"
|
||||
:wide true
|
||||
:class (stl/css :radio-buttons)}
|
||||
[:& radio-button {:icon i/size-vertical
|
||||
:value "vertical"
|
||||
:id "size-vertical"}]
|
||||
[:& radio-button {:icon i/size-horizontal
|
||||
:value "horizontal"
|
||||
:id "size-horizontal"}]]]))
|
||||
|
||||
[:> radio-buttons* {:class (stl/css :radio-buttons)
|
||||
:selected (or (d/name orientation) "")
|
||||
:on-change on-orientation-change
|
||||
:name "frame-orientation"
|
||||
:options [{:id "size-vertical"
|
||||
:icon i/size-vertical
|
||||
:label (tr "workspace.options.orientation.vertical")
|
||||
:value "vertical"}
|
||||
{:id "size-horizontal"
|
||||
:icon i/size-horizontal
|
||||
:label (tr "workspace.options.orientation.horizontal")
|
||||
:value "horizontal"}]}]]))
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.shortcuts :as sc]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -42,68 +43,59 @@
|
||||
(when-not (and disabled-align disabled-distribute)
|
||||
[:div {:class (stl/css :align-options)}
|
||||
[:div {:class (stl/css :align-group-horizontal)}
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-align)
|
||||
:disabled disabled-align
|
||||
:title (tr "workspace.align.hleft" (sc/get-tooltip :align-left))
|
||||
:data-value "hleft"
|
||||
:on-click align-objects}
|
||||
deprecated-icon/align-left]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/align-left
|
||||
:aria-label (tr "workspace.align.hleft" (sc/get-tooltip :align-left))
|
||||
:on-click align-objects
|
||||
:data-value "hleft"
|
||||
:disabled disabled-align}]
|
||||
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-align)
|
||||
:disabled disabled-align
|
||||
:title (tr "workspace.align.hcenter" (sc/get-tooltip :align-hcenter))
|
||||
:data-value "hcenter"
|
||||
:on-click align-objects}
|
||||
deprecated-icon/align-horizontal-center]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/align-horizontal-center
|
||||
:aria-label (tr "workspace.align.hcenter" (sc/get-tooltip :align-hcenter))
|
||||
:on-click align-objects
|
||||
:data-value "hcenter"
|
||||
:disabled disabled-align}]
|
||||
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-align)
|
||||
:disabled disabled-align
|
||||
:title (tr "workspace.align.hright" (sc/get-tooltip :align-right))
|
||||
:data-value "hright"
|
||||
:on-click align-objects}
|
||||
deprecated-icon/align-right]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/align-right
|
||||
:aria-label (tr "workspace.align.hright" (sc/get-tooltip :align-right))
|
||||
:on-click align-objects
|
||||
:data-value "hright"
|
||||
:disabled disabled-align}]
|
||||
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-distribute)
|
||||
:disabled disabled-distribute
|
||||
:title (tr "workspace.align.hdistribute" (sc/get-tooltip :h-distribute))
|
||||
:data-value "horizontal"
|
||||
:on-click distribute-objects}
|
||||
deprecated-icon/distribute-horizontally]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/distribute-horizontally
|
||||
:aria-label (tr "workspace.align.hdistribute" (sc/get-tooltip :h-distribute))
|
||||
:on-click distribute-objects
|
||||
:data-value "horizontal"
|
||||
:disabled disabled-distribute}]]
|
||||
|
||||
[:div {:class (stl/css :align-group-vertical)}
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-align)
|
||||
:disabled disabled-align
|
||||
:title (tr "workspace.align.vtop" (sc/get-tooltip :align-top))
|
||||
:data-value "vtop"
|
||||
:on-click align-objects}
|
||||
deprecated-icon/align-top]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/align-top
|
||||
:aria-label (tr "workspace.align.vtop" (sc/get-tooltip :align-top))
|
||||
:on-click align-objects
|
||||
:data-value "vtop"
|
||||
:disabled disabled-align}]
|
||||
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-align)
|
||||
:disabled disabled-align
|
||||
:title (tr "workspace.align.vcenter" (sc/get-tooltip :align-vcenter))
|
||||
:data-value "vcenter"
|
||||
:on-click align-objects}
|
||||
deprecated-icon/align-vertical-center]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/align-vertical-center
|
||||
:aria-label (tr "workspace.align.vcenter" (sc/get-tooltip :align-vcenter))
|
||||
:on-click align-objects
|
||||
:data-value "vcenter"
|
||||
:disabled disabled-align}]
|
||||
|
||||
[:button {:class (stl/css-case :align-button true
|
||||
:disabled disabled-align)
|
||||
:disabled disabled-align
|
||||
:title (tr "workspace.align.vbottom" (sc/get-tooltip :align-bottom))
|
||||
:data-value "vbottom"
|
||||
:on-click align-objects}
|
||||
deprecated-icon/align-bottom]
|
||||
|
||||
[:button {:title (tr "workspace.align.vdistribute" (sc/get-tooltip :v-distribute))
|
||||
:class (stl/css-case :align-button true
|
||||
:disabled disabled-distribute)
|
||||
:disabled disabled-distribute
|
||||
:data-value "vertical"
|
||||
:on-click distribute-objects}
|
||||
deprecated-icon/distribute-vertical-spacing]]])))
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/align-bottom
|
||||
:aria-label (tr "workspace.align.vbottom" (sc/get-tooltip :align-bottom))
|
||||
:on-click align-objects
|
||||
:data-value "vbottom"
|
||||
:disabled disabled-align}]
|
||||
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/distribute-vertical-spacing
|
||||
:aria-label (tr "workspace.align.vdistribute" (sc/get-tooltip :v-distribute))
|
||||
:on-click distribute-objects
|
||||
:data-value "vertical"
|
||||
:disabled disabled-distribute}]]])))
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
|
||||
.align-options {
|
||||
@include sidebar.option-grid-structure;
|
||||
height: deprecated.$s-32;
|
||||
}
|
||||
.align-group-horizontal,
|
||||
.align-group-vertical {
|
||||
@@ -26,27 +24,3 @@
|
||||
.align-group-vertical {
|
||||
grid-column: 5 / span 4;
|
||||
}
|
||||
|
||||
.align-button {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
padding: 0;
|
||||
border-radius: deprecated.$br-8;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
svg {
|
||||
stroke: var(--button-foreground-color-disabled);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--panel-background-color);
|
||||
svg {
|
||||
stroke: var(--button-foreground-color-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
@@ -103,10 +102,12 @@
|
||||
[:div {:class (stl/css-case :first-row true
|
||||
:hidden hidden?)}
|
||||
[:div {:class (stl/css :blur-info)}
|
||||
[:button {:class (stl/css-case :show-more true
|
||||
:selected more-options?)
|
||||
:on-click toggle-more-options}
|
||||
deprecated-icon/menu]
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:class (stl/css :show-more)
|
||||
:aria-label (tr "labels.options")
|
||||
:aria-pressed more-options?
|
||||
:on-click toggle-more-options
|
||||
:icon i/menu}]
|
||||
[:span {:class (stl/css :label)}
|
||||
(tr "workspace.options.blur-options.title")]]
|
||||
[:div {:class (stl/css :actions)}
|
||||
|
||||
@@ -37,21 +37,7 @@
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--input-details-color);
|
||||
.show-more {
|
||||
@extend .button-secondary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
box-sizing: border-box;
|
||||
border: deprecated.$s-1 solid var(--button-secondary-background-color-rest);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
}
|
||||
&.selected {
|
||||
background-color: var(--button-radio-background-color-active);
|
||||
svg {
|
||||
stroke: var(--button-radio-foreground-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
.label {
|
||||
@include deprecated.bodySmallTypography;
|
||||
|
||||
@@ -15,15 +15,12 @@
|
||||
[app.main.data.workspace.shortcuts :as sc]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private flatten-icon
|
||||
(deprecated-icon/icon-xref :boolean-flatten (stl/css :flatten-icon)))
|
||||
|
||||
(mf/defc bool-options*
|
||||
[{:keys [total-selected shapes shapes-with-children]}]
|
||||
(let [head (first shapes)
|
||||
@@ -70,41 +67,40 @@
|
||||
(st/emit! (dwb/change-bool-type head-id bool-type)))))))
|
||||
|
||||
flatten-objects
|
||||
(mf/use-fn #(st/emit! (dwps/convert-selected-to-path)))]
|
||||
(mf/use-fn
|
||||
#(st/emit! (dwps/convert-selected-to-path)))]
|
||||
|
||||
(when (not (and disabled-bool-btns disabled-flatten))
|
||||
[:div {:class (stl/css :boolean-options)}
|
||||
[:div {:class (stl/css :bool-group)}
|
||||
[:& radio-buttons {:selected (d/name head-bool-type)
|
||||
:class (stl/css :boolean-radio-btn)
|
||||
:on-change on-change
|
||||
:name "bool-options"}
|
||||
[:& radio-button {:icon i/boolean-union
|
||||
:value "union"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :bool-union) ")")
|
||||
:id "bool-opt-union"}]
|
||||
[:& radio-button {:icon i/boolean-difference
|
||||
:value "difference"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :bool-difference) ")")
|
||||
:id "bool-opt-differente"}]
|
||||
[:& radio-button {:icon i/boolean-intersection
|
||||
:value "intersection"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
|
||||
:id "bool-opt-intersection"}]
|
||||
[:& radio-button {:icon i/boolean-exclude
|
||||
:value "exclude"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
|
||||
:id "bool-opt-exclude"}]]]
|
||||
[:div {:class (stl/css :boolean-group)}
|
||||
[:> radio-buttons* {:class (stl/css :boolean-radio-btn)
|
||||
:variant "ghost"
|
||||
:selected (d/name head-bool-type)
|
||||
:on-change on-change
|
||||
:name "bool-options"
|
||||
:options [{:id "bool-opt-union"
|
||||
:icon i/boolean-union
|
||||
:label (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :bool-union) ")")
|
||||
:value "union"
|
||||
:disabled disabled-bool-btns}
|
||||
{:id "bool-opt-differente"
|
||||
:icon i/boolean-difference
|
||||
:label (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :bool-difference) ")")
|
||||
:value "difference"
|
||||
:disabled disabled-bool-btns}
|
||||
{:id "bool-opt-intersection"
|
||||
:icon i/boolean-intersection
|
||||
:label (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
|
||||
:value "intersection"
|
||||
:disabled disabled-bool-btns}
|
||||
{:id "bool-opt-exclude"
|
||||
:icon i/boolean-exclude
|
||||
:label (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
|
||||
:value "exclude"
|
||||
:disabled disabled-bool-btns}]}]]
|
||||
|
||||
[:button
|
||||
{:title (tr "workspace.shape.menu.flatten")
|
||||
:class (stl/css-case
|
||||
:flatten-button true
|
||||
:disabled disabled-flatten)
|
||||
:disabled disabled-flatten
|
||||
:on-click flatten-objects}
|
||||
flatten-icon]])))
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/boolean-flatten
|
||||
:aria-label (tr "workspace.shape.menu.flatten")
|
||||
:on-click flatten-objects
|
||||
:disabled disabled-flatten}]])))
|
||||
|
||||
@@ -4,45 +4,18 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
|
||||
.boolean-options {
|
||||
@include sidebar.option-grid-structure;
|
||||
height: var(--sp-xxxl);
|
||||
}
|
||||
|
||||
.bool-group {
|
||||
.boolean-group {
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
grid-column: 1 / span 4;
|
||||
}
|
||||
|
||||
.flatten-button {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
border-radius: deprecated.$br-8;
|
||||
grid-column: 5 / span 1;
|
||||
--flatten-icon-foreground-color: var(--icon-foreground);
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
--flatten-icon-foreground-color: var(--button-foreground-color-disabled);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--panel-background-color);
|
||||
--flatten-icon-foreground-color: var(--button-foreground-color-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flatten-icon {
|
||||
@extend .button-icon;
|
||||
stroke: var(--flatten-icon-foreground-color);
|
||||
}
|
||||
|
||||
.boolean-radio-btn {
|
||||
background-color: transparent;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
@@ -145,9 +145,9 @@
|
||||
:on-change on-radius-r3-change
|
||||
:value (:r3 values)}]]])
|
||||
|
||||
[:> icon-button* {:class (stl/css-case :selected radius-expanded)
|
||||
:variant "ghost"
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:on-click toggle-radius-mode
|
||||
:aria-pressed radius-expanded
|
||||
:aria-label (if radius-expanded
|
||||
(tr "workspace.options.radius.hide-all-corners")
|
||||
(tr "workspace.options.radius.show-single-corners"))
|
||||
|
||||
@@ -28,12 +28,6 @@
|
||||
@include deprecated.bodySmallTypography;
|
||||
}
|
||||
|
||||
.selected {
|
||||
border-color: var(--button-icon-border-color-selected);
|
||||
background-color: var(--button-icon-background-color-selected);
|
||||
color: var(--button-icon-foreground-color-selected);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-inline: deprecated.$s-4;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row*]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -49,16 +50,16 @@
|
||||
• :prop → the property type (:fill, :stroke, :shadow, etc.)
|
||||
• :shape-id → the UUID of the shape using this color
|
||||
• :index → index of the color in the shape's fill/stroke list
|
||||
|
||||
|
||||
Example of groups:
|
||||
{
|
||||
{:color \"#9f2929\", :opacity 0.3, :token-name \"asd2\" :has-token-applied true}
|
||||
[{:prop :fill, :shape-id #uuid \"d0231035-25c9-80d5-8006-eae4c3dff32e\", :index 0}]
|
||||
|
||||
|
||||
{:color \"#1b54b6\", :opacity 1}
|
||||
[{:prop :fill, :shape-id #uuid \"aab34f9a-98c1-801a-8006-eae5e8236f1b\", :index 0}]
|
||||
}
|
||||
|
||||
|
||||
This structure allows fast lookups of all shapes using the same visual color,
|
||||
regardless of whether it comes from local fills, strokes or shadow-colors."
|
||||
|
||||
@@ -217,8 +218,8 @@
|
||||
:origin :color-selection
|
||||
:on-close on-close}]))
|
||||
(when (and (false? @expand-lib-color) (< 3 (count library-colors)))
|
||||
[:button {:class (stl/css :more-colors-btn)
|
||||
:on-click #(reset! expand-lib-color true)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click #(reset! expand-lib-color true)}
|
||||
(tr "workspace.options.more-lib-colors")])]
|
||||
|
||||
[:div {:class (stl/css :selected-color-group)}
|
||||
@@ -235,8 +236,8 @@
|
||||
:on-close on-close}])
|
||||
|
||||
(when (and (false? @expand-color) (< 3 (count colors)))
|
||||
[:button {:class (stl/css :more-colors-btn)
|
||||
:on-click #(reset! expand-color true)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click #(reset! expand-color true)}
|
||||
(tr "workspace.options.more-colors")])]
|
||||
|
||||
[:div {:class (stl/css :selected-color-group)}
|
||||
@@ -259,6 +260,6 @@
|
||||
|
||||
(when (and (false? @expand-token-color)
|
||||
(< 3 (count token-colors)))
|
||||
[:button {:class (stl/css :more-colors-btn)
|
||||
:on-click #(reset! expand-token-color true)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click #(reset! expand-token-color true)}
|
||||
(tr "workspace.options.more-token-colors")])]])]))
|
||||
|
||||
@@ -40,7 +40,5 @@
|
||||
}
|
||||
|
||||
.more-colors-btn {
|
||||
@extend .button-secondary;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
height: deprecated.$s-32;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
@@ -37,6 +36,7 @@
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.ds.controls.switch :refer [switch*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
@@ -794,15 +794,17 @@
|
||||
[:div {:class (stl/css :swap-library)}
|
||||
[:div {:class (stl/css :swap-library-title)}
|
||||
[:div {:class (stl/css :swap-library-name)} current-lib-name]
|
||||
[:& radio-buttons {:selected (if (:listing-thumbs? filters) "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "swap-listing-style"}
|
||||
[:& radio-button {:icon i/view-as-list
|
||||
:value "list"
|
||||
:id "swap-opt-list"}]
|
||||
[:& radio-button {:icon i/flex-grid
|
||||
:value "grid"
|
||||
:id "swap-opt-grid"}]]]
|
||||
[:> radio-buttons* {:selected (if (:listing-thumbs? filters) "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "swap-listing-style"
|
||||
:options [{:id "swap-opt-list"
|
||||
:icon i/view-as-list
|
||||
:label (tr "workspace.assets.list-view")
|
||||
:value "list"}
|
||||
{:id "swap-opt-grid"
|
||||
:icon i/flex-grid
|
||||
:label (tr "workspace.assets.grid-view")
|
||||
:value "grid"}]}]]
|
||||
|
||||
(when-not (or search? (str/empty? (:path filters)))
|
||||
[:button {:class (stl/css :swap-library-back)
|
||||
@@ -897,11 +899,13 @@
|
||||
|
||||
(when menu-entries?
|
||||
[:div {:class (stl/css :pill-actions)}
|
||||
[:button {:class (stl/css-case :pill-actions-btn true
|
||||
:selected menu-open?)
|
||||
:on-click on-menu-click}
|
||||
[:> icon* {:icon-id i/menu}]]
|
||||
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:class (stl/css-case :pill-actions-btn true
|
||||
:extended subtext)
|
||||
:aria-pressed menu-open?
|
||||
:aria-label (tr "labels.options")
|
||||
:on-click on-menu-click
|
||||
:icon i/menu}]
|
||||
[:& dropdown {:show menu-open?
|
||||
:on-close on-menu-close}
|
||||
[:ul {:class (stl/css-case :pill-actions-dropdown true
|
||||
|
||||
@@ -587,14 +587,9 @@
|
||||
}
|
||||
|
||||
.pill-actions-btn {
|
||||
@extend .button-secondary;
|
||||
cursor: unset;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
border-radius: 0 $br-8 $br-8 0;
|
||||
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
&.extended {
|
||||
block-size: $sz-48;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[cuerdas.core :as str]
|
||||
@@ -61,6 +61,7 @@
|
||||
constraints-h (or (get values :constraints-h) (gsh/default-constraints-h values))
|
||||
constraints-v (or (get values :constraints-v) (gsh/default-constraints-v values))
|
||||
|
||||
fixed-scroll? (d/nilv (:fixed-scroll values) false)
|
||||
|
||||
on-constraint-button-clicked
|
||||
(mf/use-fn
|
||||
@@ -218,16 +219,8 @@
|
||||
:options options-v
|
||||
:on-change on-constraint-v-select-changed}]]
|
||||
(when first-level?
|
||||
[:div {:class (stl/css :checkbox)}
|
||||
|
||||
[:label {:for "fixed-on-scroll"
|
||||
:class (stl/css-case :checked (:fixed-scroll values))}
|
||||
[:span {:class (stl/css-case :check-mark true
|
||||
:checked (:fixed-scroll values))}
|
||||
(when (:fixed-scroll values)
|
||||
deprecated-icon/status-tick)]
|
||||
(tr "workspace.options.constraints.fix-when-scrolling")
|
||||
[:input {:type "checkbox"
|
||||
:id "fixed-on-scroll"
|
||||
:checked (:fixed-scroll values)
|
||||
:on-change on-fixed-scroll-clicked}]]])]])])))
|
||||
[:> checkbox* {:id "fixed-on-scroll"
|
||||
:class (stl/css :checkbox)
|
||||
:label (tr "workspace.options.constraints.fix-when-scrolling")
|
||||
:checked fixed-scroll?
|
||||
:on-change on-fixed-scroll-clicked}])]])])))
|
||||
|
||||
@@ -137,36 +137,4 @@
|
||||
margin-bottom: deprecated.$s-8;
|
||||
margin-top: deprecated.$s-8;
|
||||
padding-left: 0;
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
@include deprecated.bodySmallTypography;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-2;
|
||||
cursor: pointer;
|
||||
color: var(--input-checkbox-text-foreground-color);
|
||||
.check-mark {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-16;
|
||||
height: deprecated.$s-16;
|
||||
border-radius: deprecated.$br-6;
|
||||
background-color: var(--input-checkbox-inactive-background-color);
|
||||
&.checked {
|
||||
background-color: var(--input-checkbox-background-color-active);
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--input-details-color);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border-color: var(--input-checkbox-border-color-hover);
|
||||
}
|
||||
&:focus {
|
||||
border-color: var(--input-checkbox-border-color-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.exports.assets]
|
||||
@@ -263,12 +264,10 @@
|
||||
:icon i/remove}]])])
|
||||
|
||||
(when (or (= :multiple exports) (seq exports))
|
||||
[:button
|
||||
{:on-click (when-not in-progress? on-download)
|
||||
:class (stl/css-case
|
||||
:export-btn true
|
||||
:btn-disabled in-progress?)
|
||||
:disabled in-progress?}
|
||||
[:> button* {:variant "secondary"
|
||||
:class (stl/css :export-btn)
|
||||
:on-click (when-not in-progress? on-download)
|
||||
:disabled in-progress?}
|
||||
(if in-progress?
|
||||
(tr "workspace.options.exporting-object")
|
||||
(tr "workspace.options.export-object" (c (count shapes-with-exports))))])])]))
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
.multiple-exports {
|
||||
@include deprecated.flexRow;
|
||||
grid-column: 1 / span 9;
|
||||
grid-column: 1 / span 8;
|
||||
.label {
|
||||
@extend .mixed-bar;
|
||||
}
|
||||
@@ -76,8 +76,6 @@
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
@extend .button-secondary;
|
||||
@include deprecated.uppercaseTitleTipography;
|
||||
grid-column: 1 / span 9;
|
||||
height: deprecated.$s-32;
|
||||
grid-column: 1 / span 8;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -252,16 +252,9 @@
|
||||
(when (or (= type :frame)
|
||||
(and (= type :multiple)
|
||||
(some? hide-on-export)))
|
||||
[:div {:class (stl/css :fill-checkbox)}
|
||||
[:label {:for "show-fill-on-export"
|
||||
:class (stl/css-case :global/checked (not hide-on-export))}
|
||||
[:span {:class (stl/css-case :check-mark true
|
||||
:checked (not hide-on-export))}
|
||||
(when (not hide-on-export)
|
||||
deprecated-icon/status-tick)]
|
||||
(tr "workspace.options.show-fill-on-export")
|
||||
[:input {:type "checkbox"
|
||||
:id "show-fill-on-export"
|
||||
:ref checkbox-ref
|
||||
:checked (not hide-on-export)
|
||||
:on-change on-change-show-on-export}]]])])]))
|
||||
[:> checkbox* {:ref checkbox-ref
|
||||
:id "show-fill-on-export"
|
||||
:class (stl/css :fill-checkbox)
|
||||
:label (tr "workspace.options.show-fill-on-export")
|
||||
:checked (not hide-on-export)
|
||||
:on-change on-change-show-on-export}])])]))
|
||||
|
||||
@@ -50,14 +50,5 @@
|
||||
}
|
||||
|
||||
.fill-checkbox {
|
||||
// TODO create a checkbox component in the DS
|
||||
@extend .input-checkbox;
|
||||
padding-inline-start: var(--sp-s);
|
||||
span.checked {
|
||||
background-color: var(--color-accent-primary);
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--color-background-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,10 +148,13 @@
|
||||
[:div {:class (stl/css :grid-title)}
|
||||
[:div {:class (stl/css-case :option-row true
|
||||
:hidden is-hidden?)}
|
||||
[:button {:class (stl/css-case :show-options true
|
||||
:selected open?)
|
||||
:on-click toggle-advanced-options}
|
||||
deprecated-icon/menu]
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:icon i/menu
|
||||
:class (stl/css :show-options)
|
||||
:aria-pressed open?
|
||||
:aria-label (tr "labels.options")
|
||||
:on-click toggle-advanced-options
|
||||
:disabled is-hidden?}]
|
||||
[:div {:class (stl/css :type-select-wrapper)}
|
||||
[:& select
|
||||
{:class (stl/css :grid-type-select)
|
||||
@@ -204,10 +207,11 @@
|
||||
:origin :guides
|
||||
:on-change handle-change-color
|
||||
:on-detach handle-detach-color}]
|
||||
[:button {:class (stl/css-case :show-more-options true
|
||||
:selected show-more-options?)
|
||||
:on-click toggle-more-options}
|
||||
deprecated-icon/menu]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/menu
|
||||
:aria-pressed show-more-options?
|
||||
:aria-label (tr "labels.options")
|
||||
:on-click toggle-more-options}]]
|
||||
(when show-more-options?
|
||||
[:div {:class (stl/css :second-row)}
|
||||
[:button {:class (stl/css-case :btn-options true
|
||||
@@ -284,11 +288,12 @@
|
||||
:className (stl/css :numeric-input)
|
||||
:value (or (:margin params) 0)}]]
|
||||
|
||||
[:button {:class (stl/css-case :show-more-options true
|
||||
:selected show-more-options?)
|
||||
:on-click toggle-more-options
|
||||
:disabled is-default}
|
||||
deprecated-icon/menu]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:icon i/menu
|
||||
:aria-pressed show-more-options?
|
||||
:aria-label (tr "labels.options")
|
||||
:on-click toggle-more-options
|
||||
:disabled is-default}]
|
||||
(when show-more-options?
|
||||
[:div {:class (stl/css :more-options)}
|
||||
[:button {:class (stl/css :option-btn)
|
||||
|
||||
@@ -38,18 +38,7 @@
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--input-details-color);
|
||||
.show-options {
|
||||
@extend .button-secondary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
box-sizing: border-box;
|
||||
border: deprecated.$s-1 solid var(--input-border-color);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
}
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
}
|
||||
.type-select-wrapper {
|
||||
flex-grow: 1;
|
||||
@@ -108,7 +97,6 @@
|
||||
|
||||
&.hidden {
|
||||
.show-options {
|
||||
@include deprecated.hiddenElement;
|
||||
border: deprecated.$s-1 solid var(--input-border-color-disabled);
|
||||
}
|
||||
.type-select-wrapper,
|
||||
@@ -176,17 +164,7 @@
|
||||
.color-wrapper {
|
||||
width: deprecated.$s-156;
|
||||
}
|
||||
.show-more-options {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
}
|
||||
&.selected {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
}
|
||||
|
||||
.height {
|
||||
@extend .input-element;
|
||||
@include deprecated.bodySmallTypography;
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
@@ -35,10 +36,10 @@
|
||||
:justify-self
|
||||
:area-name])
|
||||
|
||||
(mf/defc set-self-alignment
|
||||
[{:keys [is-col? alignment set-alignment] :as props}]
|
||||
(mf/defc set-self-alignment*
|
||||
[{:keys [is-col alignment set-alignment]}]
|
||||
(let [alignment (or alignment :auto)
|
||||
type (if is-col? "col" "row")
|
||||
type (if is-col "col" "row")
|
||||
|
||||
handle-set-alignment
|
||||
(mf/use-callback
|
||||
@@ -46,39 +47,35 @@
|
||||
(fn [value]
|
||||
(set-alignment (-> value keyword))))]
|
||||
|
||||
[:div {:class (stl/css :self-align-menu)}
|
||||
[:& radio-buttons {:selected (d/name alignment)
|
||||
:on-change handle-set-alignment
|
||||
[:> radio-buttons* {:class (stl/css :self-align-menu)
|
||||
:selected (d/name alignment)
|
||||
:name (dm/str "flex-align-items-" type)
|
||||
:allow-empty true
|
||||
:name (dm/str "flex-align-items-" type)}
|
||||
[:& radio-button {:value "start"
|
||||
:icon (if is-col?
|
||||
i/align-self-row-left
|
||||
i/align-self-column-top)
|
||||
:title "Align self start"
|
||||
:id (dm/str "align-self-start-" type)}]
|
||||
|
||||
[:& radio-button {:value "center"
|
||||
:icon (if is-col?
|
||||
i/align-self-row-center
|
||||
i/align-self-column-center)
|
||||
:title "Align self center"
|
||||
:id (dm/str "align-self-center-" type)}]
|
||||
|
||||
[:& radio-button {:value "end"
|
||||
:icon (if is-col?
|
||||
i/align-self-row-right
|
||||
i/align-self-column-bottom)
|
||||
:title "Align self end"
|
||||
:id (dm/str "align-self-end-" type)}]
|
||||
|
||||
[:& radio-button {:value "stretch"
|
||||
:icon (if is-col?
|
||||
i/align-self-row-stretch
|
||||
i/align-self-column-stretch)
|
||||
:title "Align self stretch"
|
||||
:id (dm/str "align-self-stretch-" type)}]]]))
|
||||
|
||||
:on-change handle-set-alignment
|
||||
:options [{:id (dm/str "align-self-start-" type)
|
||||
:icon (if is-col
|
||||
i/align-self-row-left
|
||||
i/align-self-column-top)
|
||||
:label "Align self start"
|
||||
:value "start"}
|
||||
{:id (dm/str "align-self-center-" type)
|
||||
:icon (if is-col
|
||||
i/align-self-row-center
|
||||
i/align-self-column-center)
|
||||
:label "Align self center"
|
||||
:value "center"}
|
||||
{:id (dm/str "align-self-end-" type)
|
||||
:icon (if is-col
|
||||
i/align-self-row-right
|
||||
i/align-self-column-bottom)
|
||||
:label "Align self end"
|
||||
:value "end"}
|
||||
{:id (dm/str "align-self-stretch-" type)
|
||||
:icon (if is-col
|
||||
i/align-self-row-stretch
|
||||
i/align-self-column-stretch)
|
||||
:label "Align self stretch"
|
||||
:value "stretch"}]}]))
|
||||
|
||||
(mf/defc options
|
||||
{::mf/wrap [mf/memo]}
|
||||
@@ -182,16 +179,19 @@
|
||||
|
||||
(when open?
|
||||
[:div {:class (stl/css :grid-cell-menu-container)}
|
||||
[:div {:class (stl/css :cell-mode :row)}
|
||||
[:& radio-buttons {:selected (d/name cell-mode)
|
||||
:on-change set-cell-mode
|
||||
[:> radio-buttons* {:selected (d/name cell-mode)
|
||||
:name "cell-mode"
|
||||
:wide true}
|
||||
[:& radio-button {:value "auto" :id :auto}]
|
||||
[:& radio-button {:value "manual" :id :manual}]
|
||||
[:& radio-button {:value "area"
|
||||
:id :area
|
||||
:disabled (not valid-area-cells?)}]]]
|
||||
:on-change set-cell-mode
|
||||
:options [{:id "auto"
|
||||
:label "Auto"
|
||||
:value "auto"}
|
||||
{:id "manual"
|
||||
:label "Manual"
|
||||
:value "manual"}
|
||||
{:id "area"
|
||||
:label "Area"
|
||||
:value "area"
|
||||
:disabled (not valid-area-cells?)}]}]
|
||||
|
||||
(when (= :area cell-mode)
|
||||
[:div {:class (stl/css :row)}
|
||||
@@ -261,16 +261,15 @@
|
||||
:value row-end}]]]])
|
||||
|
||||
[:div {:class (stl/css :row)}
|
||||
[:& set-self-alignment {:is-col? false
|
||||
:alignment align-self
|
||||
:set-alignment set-alignment}]
|
||||
[:& set-self-alignment {:is-col? true
|
||||
:alignment justify-self
|
||||
:set-alignment set-justify-self}]]
|
||||
[:> set-self-alignment* {:is-col false
|
||||
:alignment align-self
|
||||
:set-alignment set-alignment}]
|
||||
[:> set-self-alignment* {:is-col true
|
||||
:alignment justify-self
|
||||
:set-alignment set-justify-self}]]
|
||||
|
||||
[:div {:class (stl/css :row)}
|
||||
[:button
|
||||
{:class (stl/css :edit-grid-btn)
|
||||
:alt (tr "workspace.layout_grid.editor.options.edit-grid")
|
||||
:on-click toggle-edit-mode}
|
||||
(tr "workspace.layout_grid.editor.options.edit-grid")]]])]))
|
||||
[:> button* {:variant "secondary"
|
||||
:class (stl/css :edit-grid-btn)
|
||||
:on-click toggle-edit-mode}
|
||||
(tr "workspace.layout-grid.editor.options.edit-grid")]]])]))
|
||||
|
||||