Compare commits

..

6 Commits

Author SHA1 Message Date
María Valderrama
c5341c0378 📎 Update test 2026-01-09 15:01:18 +01:00
María Valderrama
1470f7c18b 🐛 Fix empty state message in trash page 2026-01-09 11:56:09 +01:00
Andrey Antukh
2b836f10cb 🐛 Do not show deleted files on search (#8036)
* 🐛 Do not show deleted files on search

* 💄 Add cosmetic changes to dashboard deleted files page
2026-01-09 11:11:29 +01:00
Andrey Antukh
1ae0f3fc87 Merge pull request #8037 from penpot/niwinz-staging-project-name-fix
🐛 Fix long project name visual problem on dashboard
2026-01-08 17:41:37 +01:00
Eva Marco
e13c203b8d ♻️ Refactor scss file 2026-01-08 16:35:56 +01:00
Andrey Antukh
97e4f4c424 🐛 Fix long project name visual problem on dashboard 2026-01-08 11:40:55 +01:00
188 changed files with 27943 additions and 29801 deletions

View File

@@ -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

View File

@@ -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|Reapply).+[^.])$'
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).+[^.])$'
flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request

2
.gitignore vendored
View File

@@ -21,7 +21,6 @@
.rebel_readline_history
.repl
.shadow-cljs
.pnpm-store/
/*.jpg
/*.md
/*.png
@@ -73,7 +72,6 @@
/library/target/
/library/*.zip
/external
/penpot-nitrate
clj-profiler/
node_modules

View File

@@ -1,20 +1,5 @@
# 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)
### :bug: Bugs fixed
## 2.13.0 (Unreleased)
### :boom: Breaking changes & Deprecations
@@ -38,7 +23,6 @@
- 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)
@@ -46,6 +30,7 @@
- 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
@@ -54,6 +39,7 @@
- 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
@@ -65,6 +51,7 @@ 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
@@ -97,6 +84,7 @@ 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

View File

@@ -36,8 +36,7 @@ export PENPOT_FLAGS="\
enable-file-validation \
enable-file-schema-validation \
enable-redis-cache \
enable-subscriptions \
enable-nitrate";
enable-subscriptions";
# Default deletion delay for devenv
export PENPOT_DELETION_DELAY="24h"
@@ -56,8 +55,6 @@ export PENPOT_OBJECTS_STORAGE_BACKEND=s3
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
export PENPOT_NITRATE_BACKEND_URI=http://localhost:3000
export JAVA_OPTS="\
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Djdk.attach.allowAttachSelf \

View File

@@ -225,8 +225,6 @@
[:netty-io-threads {:optional true} ::sm/int]
[:executor-threads {:optional true} ::sm/int]
[:nitrate-backend-uri {:optional true} ::sm/uri]
;; DEPRECATED
[:assets-storage-backend {:optional true} :keyword]
[:storage-assets-fs-directory {:optional true} :string]

View File

@@ -323,7 +323,6 @@
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::rds/pool (ig/ref ::rds/pool)
:app.nitrate/client (ig/ref :app.nitrate/client)
::wrk/executor (ig/ref ::wrk/netty-executor)
::session/manager (ig/ref ::session/manager)
::ldap/provider (ig/ref ::ldap/provider)
@@ -340,9 +339,6 @@
::email/blacklist (ig/ref ::email/blacklist)
::email/whitelist (ig/ref ::email/whitelist)}
:app.nitrate/client
{::http.client/client (ig/ref ::http.client/client)}
:app.rpc/management-methods
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
@@ -352,7 +348,6 @@
::sto/storage (ig/ref ::sto/storage)
::mtx/metrics (ig/ref ::mtx/metrics)
::mbus/msgbus (ig/ref ::mbus/msgbus)
:app.nitrate/client (ig/ref :app.nitrate/client)
::rds/client (ig/ref ::rds/client)
::setup/props (ig/ref ::setup/props)}

View File

@@ -1,123 +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.nitrate
"Module that make calls to the external nitrate aplication"
(:require
[app.common.logging :as l]
[app.common.schema :as sm]
[app.config :as cf]
[app.http.client :as http]
[app.rpc :as-alias rpc]
[app.setup :as-alias setup]
[app.util.json :as json]
[clojure.core :as c]
[integrant.core :as ig]))
(def baseuri (cf/get :nitrate-backend-uri))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- coercer
[schema & {:as opts}]
(let [decode-fn (sm/decoder schema sm/json-transformer)
check-fn (sm/check-fn schema opts)]
(fn [data]
(-> data decode-fn check-fn))))
(defn- request-builder
[cfg method uri management-key profile-id]
(fn []
(http/req! cfg {:method method
:headers {"content-type" "application/json"
"accept" "application/json"
"x-shared-key" management-key
"x-profile-id" (str profile-id)}
:uri uri
:version :http1.1})))
(defn- with-retries
[handler max-retries]
(fn []
(loop [attempt 1]
(let [result (try
(handler)
(catch Exception e
(if (< attempt max-retries)
::retry
(do
;; TODO Error handling
(l/error :hint "request fail after multiple retries" :cause e)
nil))))]
(if (= result ::retry)
(recur (inc attempt))
result)))))
(defn- with-validate [handler uri schema]
(fn []
(let [coercer-http (coercer schema
:type :validation
:hint (str "invalid data received calling " uri))]
(try
(coercer-http (-> (handler) :body json/decode))
(catch Exception e
;; TODO Error handling
(l/error :hint "error validating json response" :cause e)
nil)))))
(defn- request-to-nitrate
[{:keys [::management-key] :as cfg} method uri schema {:keys [::rpc/profile-id] :as params}]
(let [full-http-call (-> (request-builder cfg method uri management-key profile-id)
(with-retries 3)
(with-validate uri schema))]
(full-http-call)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn call
[cfg method params]
(when (contains? cf/flags :nitrate)
(let [client (get cfg ::client)
method (get client method)]
(method params))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private schema:organization
[:map
[:id ::sm/text]
[:name ::sm/text]])
(defn- get-team-org
[cfg {:keys [team-id] :as params}]
(request-to-nitrate cfg :get (str baseuri "/api/teams/" (str team-id)) schema:organization params))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INITIALIZATION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ig/init-key ::client
[_ {:keys [::setup/props] :as cfg}]
(if (contains? cf/flags :nitrate)
(let [management-key (or (cf/get :management-api-key)
(get props :management-key))
cfg (assoc cfg ::management-key management-key)]
{:get-team-org (partial get-team-org cfg)})
{}))
(defmethod ig/halt-key! ::client
[_ {:keys []}]
(do :stuff))

View File

@@ -301,7 +301,6 @@
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
(->> (sv/scan-ns
'app.rpc.management.subscription
'app.rpc.management.nitrate
'app.rpc.management.exporter)
(map (partial process-method cfg "management" wrap-management))
(into {}))))

View File

@@ -19,7 +19,7 @@
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = ?
and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now())
and (p.deleted_at is null)
and (tpr.is_admin = true or
tpr.is_owner = true or
tpr.can_edit = true)
@@ -29,7 +29,7 @@
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = ?
and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now())
and (p.deleted_at is null)
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)
@@ -47,7 +47,7 @@
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
inner join projects as pr on (f.project_id = pr.id)
where f.name ilike ('%' || ? || '%')
and (f.deleted_at is null or f.deleted_at > now())
and (f.deleted_at is null)
order by f.created_at asc")
(defn search-files

View File

@@ -23,7 +23,6 @@
[app.main :as-alias main]
[app.media :as media]
[app.msgbus :as mbus]
[app.nitrate :as nitrate]
[app.rpc :as-alias rpc]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]
@@ -173,12 +172,6 @@
(map decode-row)
(map process-permissions)))
(defn- add-org-to-team
[cfg team params]
(let [params (assoc (or params {}) :team-id (:id team))
org (nitrate/call cfg :get-team-org params)]
(assoc team :organization-id (:id org) :organization-name (:name org))))
(defn get-teams
[conn profile-id]
(let [profile (profile/get-profile conn profile-id)
@@ -197,9 +190,7 @@
::sm/params schema:get-teams}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
(dm/with-open [conn (db/open pool)]
(cond->> (get-teams conn profile-id)
(contains? cf/flags :nitrate)
(map #(add-org-to-team cfg % params)))))
(get-teams conn profile-id)))
(def ^:private sql:get-owned-teams
"SELECT t.id, t.name,

View File

@@ -1,112 +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.rpc.management.nitrate
"Internal Nitrate HTTP API.
Provides authenticated access to organization management and token validation endpoints.
All requests must include a valid shared key token in the `x-shared-key` header, and
a cookie `auth-token` with the user token.
They will return `401 Unauthorized` if the shared key or user token are invalid."
(:require
[app.common.schema :as sm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.msgbus :as mbus]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as doc]
[app.util.services :as sv]))
;; ---- API: authenticate
(def ^:private schema:profile
[:map
[:id ::sm/uuid]
[:name :string]
[:email :string]
[:photo-url :string]])
(sv/defmethod ::authenticate
"Authenticate an user
@api GET /authenticate
@returns
200 OK: Returns the authenticated user."
{::doc/added "2.12"
::sm/result schema:profile}
[cfg {:keys [::rpc/profile-id] :as params}]
(let [profile (profile/get-profile cfg profile-id)]
{:id (get profile :id)
:name (get profile :fullname)
:email (get profile :email)
:photo-url (files/resolve-public-uri (get profile :photo-id))}))
;; ---- API: get-teams
(def ^:private sql:get-teams
"SELECT t.*
FROM team AS t
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
WHERE tpr.profile_id = ?
AND tpr.is_owner = 't'
AND t.is_default = 'f'
AND t.deleted_at is null;")
(def ^:private schema:team
[:map
[:id ::sm/uuid]
[:name :string]])
(def ^:private schema:get-teams-result
[:vector schema:team])
(sv/defmethod ::get-teams
"List teams for which current user is owner.
@api GET /get-teams
@returns
200 OK: Returns the list of teams for the user."
{::doc/added "2.12"
::sm/result schema:get-teams-result}
[cfg {:keys [::rpc/profile-id]}]
(when (contains? cf/flags :nitrate)
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
(->> (db/exec! cfg [sql:get-teams current-user-id])
(map #(select-keys % [:id :name]))))))
;; ---- API: notify-team-change
(def ^:private schema:notify-team-change
[:map
[:id ::sm/uuid]
[:organization-id ::sm/text]])
(sv/defmethod ::notify-team-change
"Notify to Penpot a team change from nitrate
@api POST /notify-team-change
@returns
200 OK"
{::doc/added "2.12"
::sm/params schema:notify-team-change
::rpc/auth false}
[cfg {:keys [id organization-id organization-name]}]
(when (contains? cf/flags :nitrate)
(let [msgbus (::mbus/msgbus cfg)]
(mbus/pub! msgbus
;;TODO There is a bug on dashboard with teams notifications.
;;For now we send it to uuid/zero instead of team-id
:topic uuid/zero
:message {:type :team-org-change
:team-id id
:organization-id organization-id
:organization-name organization-name}))))

View File

@@ -145,10 +145,7 @@
;; A temporal flag, enables backend code use more extensivelly
;; redis for caching data
:redis-cache
;; Activates the nitrate module
:nitrate})
:redis-cache})
(def all-flags
(set/union email login varia))

View File

@@ -132,94 +132,3 @@ 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)))

View File

@@ -47,18 +47,6 @@
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -570,18 +558,3 @@
"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))

View File

@@ -909,8 +909,7 @@ 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, as a sequence")
(get-all-tokens-map [_] "all tokens in the lib, as a map name -> token")
(get-all-tokens [_] "all tokens in the lib")
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name"))
(declare parse-multi-set-dtcg-json)
@@ -1307,10 +1306,6 @@ 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)))))

View File

@@ -41,9 +41,4 @@ tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/backend' enter C-l
tmux send-keys -t penpot './scripts/start-dev' enter
tmux new-window -t penpot:5 -n 'nitrate'
tmux select-window -t penpot:5
tmux send-keys -t penpot 'cd penpot/penpot-nitrate' enter C-l
tmux send-keys -t penpot 'pnpm dev --host' enter
tmux -2 attach-session -t penpot

View File

@@ -29,9 +29,8 @@ update_flags /var/www/app/js/config.js
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE:-367001600} # Default to 350MiB
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"

View File

@@ -139,14 +139,6 @@ http {
proxy_pass $PENPOT_BACKEND_URI/ws/notifications;
}
location /control-center {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass $PENPOT_NITRATE_URI$request_uri;
}
include /etc/nginx/overrides/server.d/*.conf;
location / {

View File

@@ -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.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.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.toggleToolbarButton = page.getByRole("button", {
name: "Toggle toolbar",
});

View File

@@ -20,12 +20,7 @@ test.describe("Dashboard Deleted Page", () => {
// Navigate directly to deleted page
await dashboardPage.goToDeleted();
// Check for the restore all and clear trash buttons
await expect(
page.getByRole("button", { name: "Restore All" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Clear trash" }),
).toBeVisible();
// Check for the delete-page-section element
await expect(page.getByTestId("deleted-page-section")).toBeVisible();
});
});

View File

@@ -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.getByTestId("behaviour-h-auto");
const vAuto = await workspacePage.page.getByTestId("behaviour-v-auto");
const hAuto = await workspacePage.page.getByTitle("Fit content (Horizontal)");
const vAuto = await workspacePage.page.getByTitle("Fit content (Vertical)");
await expect(vAuto.locator("input")).toBeChecked();
await expect(hAuto.locator("input")).toBeChecked();

View File

@@ -40,7 +40,6 @@ const setupEmptyTokensFile = async (page, options = {}) => {
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
tokenSetItems: workspacePage.tokenSetItems,
tokensSidebar: workspacePage.tokensSidebar,
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
};
@@ -111,12 +110,15 @@ const checkInputFieldWithError = async (
).toBeVisible();
};
const checkInputFieldWithoutError = async (inputLocator) => {
const checkInputFieldWithoutError = async (
tokenThemeUpdateCreateModal,
inputLocator,
) => {
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
};
const testTokenCreationFlow = async (
async function testTokenCreationFlow(
page,
{
tokenLabel,
@@ -130,7 +132,7 @@ const testTokenCreationFlow = async (
resolvedValueText,
secondResolvedValueText,
},
) => {
) {
const invalidValueError = "Invalid token value";
const emptyNameError = "Name should be at least 1 character";
const selfReferenceError = "Token has self reference";
@@ -240,45 +242,7 @@ const testTokenCreationFlow = async (
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 ({
@@ -434,12 +398,15 @@ 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, tokensSidebar } =
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page);
await tokensSidebar
.getByRole("button", { name: "Add Token: Color" })
.click();
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const addTokenButton = tokensTabPanel.getByRole("button", {
name: `Add Token: Color`,
});
await addTokenButton.click();
await expect(tokensUpdateCreateModal).toBeVisible();
// Placeholder checks
@@ -504,34 +471,38 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "color.primary");
await expect(
tokensTabPanel.getByRole("button", {
name: "color.primary",
}),
).toBeEnabled();
// Create token referencing the previous one with keyboard
await tokensSidebar
await tokensTabPanel
.getByRole("button", { name: "Add Token: Color" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
await nameField.click();
await nameField.fill("secondary");
await nameField.fill("color.secondary");
await nameField.press("Tab");
await valueField.click();
await valueField.fill("{color.primary}");
await expect(submitButton).toBeEnabled();
await submitButton.press("Enter");
await nameField.press("Enter");
await expect(
tokensSidebar.getByRole("button", {
name: "secondary",
tokensTabPanel.getByRole("button", {
name: "color.secondary",
}),
).toBeEnabled();
// Tokens tab panel should have two tokens with the color red / #ff0000
await expect(
tokensSidebar.getByRole("button", { name: "#ff0000" }),
tokensTabPanel.getByRole("button", { name: "#ff0000" }),
).toHaveCount(2);
// Global set has been auto created and is active
@@ -547,7 +518,7 @@ test.describe("Tokens: Tokens Tab", () => {
).toHaveAttribute("aria-checked", "true");
// Check color picker
await tokensSidebar
await tokensTabPanel
.getByRole("button", { name: "Add Token: Color" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
@@ -1108,7 +1079,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" });
@@ -1536,15 +1507,24 @@ test.describe("Tokens: Tokens Tab", () => {
test("User edits token and auto created set show up in the sidebar", async ({
page,
}) => {
const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
const {
workspacePage,
tokensUpdateCreateModal,
tokenThemesSetsSidebar,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page);
await expect(tokensSidebar).toBeVisible();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const tokensColorGroup = tokensSidebar.getByRole("button", {
name: "Color 92",
});
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await colorToken.click({ button: "right" });
@@ -1561,10 +1541,8 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensUpdateCreateModal).not.toBeVisible();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
const colorTokenChanged = tokensSidebar.getByRole("button", {
name: "changed",
name: "colors.blue.100.changed",
});
await expect(colorTokenChanged).toBeVisible();
});
@@ -1655,10 +1633,11 @@ test.describe("Tokens: Tokens Tab", () => {
});
test("User creates grouped color token", async ({ page }) => {
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page);
await tokensSidebar
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
await tokensTabPanel
.getByRole("button", { name: "Add Token: Color" })
.click();
@@ -1670,7 +1649,7 @@ test.describe("Tokens: Tokens Tab", () => {
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await nameField.click();
await nameField.fill("dark.primary");
await nameField.fill("color.dark.primary");
await valueField.click();
await valueField.fill("red");
@@ -1681,9 +1660,7 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
});
test("User cant create regular token with value missing", async ({
@@ -1699,6 +1676,7 @@ 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",
});
@@ -1708,7 +1686,7 @@ test.describe("Tokens: Tokens Tab", () => {
// Fill in name but leave value empty
await nameField.click();
await nameField.fill("primary");
await nameField.fill("color.primary");
// Submit button should remain disabled when value is empty
await expect(submitButton).toBeDisabled();
@@ -1726,6 +1704,7 @@ test.describe("Tokens: Tokens Tab", () => {
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.click();
@@ -1775,10 +1754,15 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const tokensColorGroup = tokensSidebar.getByRole("button", {
name: "Color 92",
});
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await colorToken.click({ button: "right" });
@@ -1798,10 +1782,15 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const tokensColorGroup = tokensSidebar.getByRole("button", {
name: "Color 92",
});
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await colorToken.click({ button: "right" });
@@ -1814,7 +1803,8 @@ test.describe("Tokens: Tokens Tab", () => {
});
test("User fold/unfold color tokens", async ({ page }) => {
const { tokensSidebar } = await setupTokensFile(page);
const { tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
await expect(tokensSidebar).toBeVisible();
@@ -1824,10 +1814,8 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await tokensColorGroup.click();
@@ -2230,10 +2218,13 @@ test.describe("Tokens: Apply token", () => {
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "color", "colors.black");
await tokensSidebar
.getByRole("button")
.filter({ hasText: "Color" })
.click();
await tokensSidebar
.getByRole("button", { name: "black" })
.getByRole("button", { name: "colors.black" })
.click({ button: "right" });
await tokenContextMenuForToken.getByText("Fill").click();
@@ -2418,12 +2409,10 @@ test.describe("Tokens: Apply token", () => {
await nameField.fill(newTokenTitle);
const referenceTabButton =
tokensUpdateCreateModal.getByRole('button', { name: 'Use a reference' });
tokensUpdateCreateModal.getByTestId("reference-opt");
referenceTabButton.click();
const referenceField = tokensUpdateCreateModal.getByRole('textbox', {
name: 'Reference'
});
const referenceField = tokensUpdateCreateModal.getByLabel("Reference");
await referenceField.fill("{Full}");
const submitButton = tokensUpdateCreateModal.getByRole("button", {
@@ -2473,7 +2462,7 @@ test.describe("Tokens: Apply token", () => {
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("primary");
await nameField.fill("shadow.primary");
// User adds first shadow with a color from the color ramp
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
@@ -2720,11 +2709,9 @@ test.describe("Tokens: Apply token", () => {
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
unfoldTokenTree(tokensSidebar, "shadow", "primary");
// Verify token appears in sidebar
const shadowToken = tokensSidebar.getByRole("button", {
name: "primary",
name: "shadow.primary",
});
await expect(shadowToken).toBeEnabled();
@@ -2742,639 +2729,3 @@ 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.getByText("Drop shadow");
await expect(shadowSection).toBeVisible();
// Click to expand the shadow options (the menu button)
const shadowMenuButton = workspacePage.rightSidebar
.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
// // Find the color input - it should be a textbox with a 6-character hex value
// // We look for all textboxes and find the one with a hex color pattern
// const allInputs = await workspacePage.rightSidebar
// .locator('input[type="text"]')
// .all();
// let colorInput = null;
// for (const input of allInputs) {
// const value = await input.inputValue().catch(() => '');
// if (/^[A-Fa-f0-9]{6}$/.test(value)) {
// colorInput = input;
// break;
// }
// }
// 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}");
});
});
});

View File

@@ -332,33 +332,24 @@ test("Copy/paste properties", async ({ page, context }) => {
await page.getByText("Copy/Paste as").hover();
await page.getByText("Paste properties").click();
await page
.getByTestId("layer-item")
.getByText("Rectangle")
.first()
.click({ button: "right" });
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.getByText("Copy/Paste as").hover();
await page.getByText("Paste properties").click();
await page
.getByTestId("layer-item")
.getByText("Board")
.locator("div")
.filter({ hasText: "Path" })
.nth(1)
.click({ button: "right" });
await page.getByText("Copy/Paste as").hover();
await page.getByText("Paste properties").click();
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("Ellipse").click({ button: "right" });
await page.getByText("Copy/Paste as").hover();
await page.getByText("Paste properties").click();
});

View File

@@ -245,6 +245,13 @@
--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);
@@ -417,6 +424,13 @@
--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);

View File

@@ -683,24 +683,12 @@
(rx/of (dcm/change-team-role params)
(modal/hide)))))
(defn handle-change-team-org
[{:keys [team-id organization-id organization-name] :as message}]
(ptk/reify ::handle-change-team-org
ptk/UpdateEvent
(update [_ state]
(if (contains? (:teams state) team-id)
(-> state
(assoc-in [:teams team-id :organization-id] organization-id)
(assoc-in [:teams team-id :organization-name] organization-name))
state))))
(defn- process-message
[{:keys [type] :as msg}]
(case type
:notification (dcm/handle-notification msg)
:team-role-change (handle-change-team-role msg)
:team-membership-change (dcm/team-membership-change msg)
:team-org-change (handle-change-team-org msg)
nil))

View File

@@ -74,7 +74,7 @@
(when unknown-tokens
(st/emit! (show-unknown-types-warning unknown-tokens)))
(try
(->> (ctob/get-all-tokens-map tokens-lib)
(->> (ctob/get-all-tokens tokens-lib)
(sd/resolve-tokens-with-verbose-errors)
(rx/map (fn [_]
tokens-lib))

View File

@@ -11,7 +11,6 @@
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.logic.tokens :as clt]
[app.common.path-names :as cpn]
[app.common.types.shape :as cts]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
@@ -23,7 +22,6 @@
[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)
@@ -462,35 +460,12 @@
;; TOKEN UI OPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn clean-tokens-paths
[]
(ptk/reify ::clean-tokens-paths
(defn set-token-type-section-open
[token-type open?]
(ptk/reify ::set-token-type-section-open
ptk/UpdateEvent
(update [_ state]
(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)))))))))
(update-in state [:workspace-tokens :open-status-by-type] assoc token-type open?))))
(defn assign-token-context-menu
[{:keys [position] :as params}]

View File

@@ -1,177 +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.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-name (:name 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-name (:source-token-name ref)]
(when-let [{:keys [token set]} (some #(when (= (:name (:token %)) source-token-name) %) 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)))

View File

@@ -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]} input-ref]
[{:keys [accept multi label-text label-class input-id on-selected data-testid] :as props} input-ref]
(let [opt-pick-one #(if multi % (first %))
on-files-selected

View 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.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]]))

View File

@@ -0,0 +1,79 @@
// 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);
}
}
}

View File

@@ -32,6 +32,27 @@
(def ^:private menu-icon
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
(defn- on-restore-project
[project]
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
(st/emit! (modal/show
{:type :confirm
:title (tr "dashboard.restore-project-confirmation.title")
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
:accept-style :primary
:accept-label (tr "labels.continue")
:on-accept on-accept}))))
(defn- on-delete-project
[project]
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
(st/emit! (modal/show
{:type :confirm
:title (tr "dashboard.delete-forever-confirmation.title")
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
:accept-label (tr "dashboard.delete-forever-confirmation.title")
:on-accept accept-fn}))))
(mf/defc header*
{::mf/props :obj
::mf/private true}
@@ -41,6 +62,7 @@
[:h1 (tr "dashboard.projects-title")]]])
(mf/defc deleted-project-menu*
{::mf/private true}
[{:keys [project show on-close top left]}]
(let [top (d/nilv top 0)
left (d/nilv left 0)
@@ -48,25 +70,13 @@
on-restore-project
(mf/use-fn
(mf/deps project)
(fn []
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
(st/emit! (modal/show {:type :confirm
:title (tr "dashboard.restore-project-confirmation.title")
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
:accept-style :primary
:accept-label (tr "labels.continue")
:on-accept on-accept})))))
(partial on-restore-project project))
on-delete-project
(mf/use-fn
(mf/deps project)
(fn []
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
(st/emit! (modal/show {:type :confirm
:title (tr "dashboard.delete-forever-confirmation.title")
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
:accept-label (tr "dashboard.delete-forever-confirmation.title")
:on-accept accept-fn})))))
(partial on-delete-project project))
options
(mf/with-memo [on-restore-project on-delete-project]
[{:name (tr "dashboard.restore-project-button")
@@ -174,8 +184,8 @@
:limit limit
:selected-files selected-files}])]]))
(mf/defc menu*
{::mf/private true}
[{:keys [team-id section]}]
(let [on-recent-click
(mf/use-fn
@@ -222,7 +232,8 @@
(some #(= (:id project) (:project-id %))
(vals deleted-map)))))
(sort-by :modified-at)
(reverse)))
(reverse)
(not-empty)))
team-id
(get team :id)
@@ -273,37 +284,44 @@
[:*
[:> header* {:team team}]
[:section {:class (stl/css :dashboard-container :no-bg)}
[:section {:class (stl/css :dashboard-container :no-bg)
:data-testid "deleted-page-section"}
[:*
[:div {:class (stl/css :no-bg)}
[:> menu* {:team-id team-id :section :dashboard-deleted}]
[:div {:class (stl/css :deleted-info-content)}
[:p {:class (stl/css :deleted-info)}
(tr "dashboard.trash-info-text-part1")
[:span {:class (stl/css :info-text-highlight)}
(tr "dashboard.trash-info-text-part2" deletion-days)]
(tr "dashboard.trash-info-text-part3")
[:br]
(tr "dashboard.trash-info-text-part4")]
[:div {:class (stl/css :deleted-options)}
[:> button* {:variant "ghost"
:type "button"
:on-click on-restore-all}
(tr "dashboard.restore-all-deleted-button")]
[:> button* {:variant "destructive"
:type "button"
:icon "delete"
:on-click on-delete-all}
(tr "dashboard.clear-trash-button")]]]
(if (seq projects)
[:*
[:div {:class (stl/css :deleted-info-content)}
[:p {:class (stl/css :deleted-info)}
(tr "dashboard.trash-info-text-part1")
[:span {:class (stl/css :info-text-highlight)}
(tr "dashboard.trash-info-text-part2" deletion-days)]
(tr "dashboard.trash-info-text-part3")
[:br]
(tr "dashboard.trash-info-text-part4")]
[:div {:class (stl/css :deleted-options)}
[:> button* {:variant "ghost"
:type "button"
:on-click on-restore-all}
(tr "dashboard.restore-all-deleted-button")]
[:> button* {:variant "destructive"
:type "button"
:icon "delete"
:on-click on-delete-all}
(tr "dashboard.clear-trash-button")]]]
(when (seq projects)
(for [{:keys [id] :as project} projects]
(let [files (when deleted-map
(->> (vals deleted-map)
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:> deleted-project-item* {:project project
:files files
:key id}])))]]]]))
(for [{:keys [id] :as project} projects]
(let [files (when deleted-map
(->> (vals deleted-map)
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:> deleted-project-item* {:project project
:files files
:key id}]))]
;; when no deleted projects
[:div {:class (stl/css :deleted-info-content)}
[:p {:class (stl/css :deleted-info)}
(tr "dashboard.trash-info-text-no-projects")]])]]]]))

View File

@@ -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)

View File

@@ -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]

View File

@@ -4,35 +4,36 @@
//
// Copyright (c) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as deprecated;
@use "common/refactor/common-dashboard";
@use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
@use "../ds/_sizes.scss" as *;
@use "../ds/z-index.scss" as *;
@use "ds/typography.scss" as t;
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/z-index.scss" as *;
@use "ds/mixins.scss" as *;
@use "ds/_utils.scss" as *;
.dashboard-container {
flex: 1 0 0;
width: 100%;
inline-size: 100%;
margin-inline-end: var(--sp-l);
border-top: $b-1 solid var(--panel-border-color);
border-block-start: $b-1 solid var(--panel-border-color);
overflow-y: auto;
padding-bottom: var(--sp-xxxl);
padding-block-end: var(--sp-xxxl);
}
.dashboard-projects {
user-select: none;
height: calc(100vh - deprecated.$s-64);
block-size: calc(100vh - px2rem(64));
}
.with-team-hero {
height: calc(100vh - deprecated.$s-280);
block-size: calc(100vh - px2rem(280));
}
.dashboard-shared {
width: calc(100vw - deprecated.$s-320);
margin-inline-end: deprecated.$s-52;
inline-size: calc(100vw - px2rem(320));
margin-inline-end: px2rem(52);
}
.search {
@@ -66,8 +67,8 @@
align-items: center;
justify-content: space-between;
gap: var(--sp-s);
width: 99%;
max-height: $sz-40;
inline-size: 99%;
max-block-size: $sz-40;
padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l);
margin-block-start: var(--sp-l);
border-radius: $br-4;
@@ -77,19 +78,19 @@
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
min-height: var(--sp-xxxl);
inline-size: 100%;
min-block-size: $sz-32;
margin-inline-start: var(--sp-s);
}
.project-name {
@include textEllipsis;
@include t.use-typography("body-large");
width: fit-content;
margin-inline-end: var(--sp-m);
line-height: 0.8;
color: var(--title-foreground-color-hover);
cursor: pointer;
height: var(--sp-l);
block-size: $sz-16;
line-height: 0.8;
margin-inline-end: var(--sp-m);
}
.info-wrapper {
@@ -116,8 +117,8 @@
.add-file-btn,
.options-btn {
@extend .button-tertiary;
height: var(--sp-xxxl);
width: var(--sp-xxxl);
block-size: $sz-32;
inline-size: $sz-32;
margin: 0 var(--sp-s);
padding: var(--sp-s);
}
@@ -129,7 +130,7 @@
}
.grid-container {
width: 100%;
inline-size: 100%;
padding: 0 var(--sp-xs);
}
@@ -139,11 +140,13 @@
.show-more {
--show-more-color: var(--button-secondary-foreground-color-rest);
@include deprecated.buttonStyle;
@include t.use-typography("body-medium");
border: none;
background: none;
cursor: pointer;
position: absolute;
top: var(--sp-s);
right: deprecated.$s-52;
inset-block-start: var(--sp-s);
inset-inline-end: px2rem(52);
display: flex;
align-items: center;
justify-content: space-between;
@@ -156,8 +159,8 @@
}
.show-more-icon {
height: var(--sp-l);
width: var(--sp-l);
block-size: $sz-16;
inline-size: $sz-16;
fill: none;
stroke: var(--show-more-color);
}
@@ -165,7 +168,7 @@
// Team hero
.team-hero {
background-color: var(--color-background-tertiary);
border-radius: deprecated.$br-8;
border-radius: $br-8;
border: none;
display: flex;
margin: var(--sp-l);
@@ -174,12 +177,11 @@
img {
border-radius: $br-4;
height: var(--sp-xl) 0;
width: auto;
inline-size: auto;
@media (max-width: 1200px) {
display: none;
width: 0;
inline-size: 0;
}
}
}
@@ -193,9 +195,8 @@
}
.title {
font-size: $sz-24;
color: var(--color-foreground-primary);
font-weight: deprecated.$fw400;
font-size: px2rem(24);
}
.info {
@@ -215,8 +216,8 @@
--close-icon-foreground-color: var(--icon-foreground);
position: absolute;
top: var(--sp-xl);
right: var(--sp-xxl);
width: var(--sp-xxl);
inset-inline-end: var(--sp-xxl);
inline-size: var(--sp-xxl);
background-color: transparent;
border: none;
cursor: pointer;
@@ -231,20 +232,20 @@
}
.invite {
height: var(--sp-xxxl);
width: deprecated.$s-180;
block-size: $sz-32;
inline-size: px2rem(180);
}
.img-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: var(--sp-xl) 0;
height: var(--sp-xl) 0;
inline-size: var(--sp-xl) 0;
block-size: var(--sp-xl) 0;
overflow: hidden;
border-radius: deprecated.$br-4;
border-radius: $br-4;
@media (max-width: 1200px) {
display: none;
width: 0;
inline-size: 0;
}
}

View File

@@ -280,8 +280,8 @@
(mf/defc teams-selector-dropdown*
{::mf/private true}
[{:keys [team profile teams show-default-team allow-create-teams allow-create-org] :rest props}]
(let [on-create-team-click
[{:keys [team profile teams] :rest props}]
(let [on-create-click
(mf/use-fn #(st/emit! (modal/show :team-form {})))
on-team-click
@@ -290,25 +290,18 @@
(let [team-id (-> (dom/get-current-target event)
(dom/get-data "value")
(uuid/parse))]
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
on-create-org-click
(mf/use-fn
(fn []
;; TODO update when org creation route is ready
(dom/open-new-window "localhost:3000/org/create")))]
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))]
[:> dropdown-menu* props
(when show-default-team
[:> dropdown-menu-item* {:on-click on-team-click
:data-value (:default-team-id profile)
:class (stl/css :team-dropdown-item)}
[:span {:class (stl/css :penpot-icon)} deprecated-icon/logo-icon]
[:> dropdown-menu-item* {:on-click on-team-click
:data-value (:default-team-id profile)
:class (stl/css :team-dropdown-item)}
[:span {:class (stl/css :penpot-icon)} deprecated-icon/logo-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
(when (= (:default-team-id profile) (:id team))
tick-icon)])
[:span {:class (stl/css :team-text)} (tr "dashboard.your-penpot")]
(when (= (:default-team-id profile) (:id team))
tick-icon)]
(for [team-item (remove :is-default (vals teams))]
[:> dropdown-menu-item* {:on-click on-team-click
@@ -329,19 +322,11 @@
(when (= (:id team-item) (:id team))
tick-icon)])
(when allow-create-teams
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-team-click
:class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]])
(when allow-create-org
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-org-click
:class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-org")]])]))
[:hr {:role "separator" :class (stl/css :team-separator)}]
[:> dropdown-menu-item* {:on-click on-create-click
:class (stl/css :team-dropdown-item :action)}
[:span {:class (stl/css :icon-wrapper)} add-icon]
[:span {:class (stl/css :team-text)} (tr "dashboard.create-new-team")]]]))
(mf/defc team-options-dropdown*
{::mf/private true}
@@ -491,80 +476,9 @@
:data-testid "delete-team"}
(tr "dashboard.delete-team")])]))
(mf/defc sidebar-org-switch*
[{:keys [team profile]}]
(let [teams (->> (mf/deref refs/teams)
vals
(group-by :organization-id)
(map (fn [[_group entries]] (first entries)))
vec
(d/index-by :id))
teams (update-vals teams
(fn [t]
(assoc t :name (str "ORG: " (:organization-name t)))))
team (assoc team :name (str "ORG: " (:organization-name team)))
show-teams-menu*
(mf/use-state false)
show-teams-menu?
(deref show-teams-menu*)
on-show-teams-click
(mf/use-fn
(fn [event]
(dom/stop-propagation event)
(swap! show-teams-menu* not)))
on-show-teams-keydown
(mf/use-fn
(fn [event]
(when (or (kbd/space? event)
(kbd/enter? event))
(dom/prevent-default event)
(dom/stop-propagation event)
(some-> (dom/get-current-target event)
(dom/click!)))))
close-teams-menu
(mf/use-fn #(reset! show-teams-menu* false))]
[:div {:class (stl/css :sidebar-team-switch)}
[:div {:class (stl/css :switch-content)}
[:button {:class (stl/css :current-team)
:on-click on-show-teams-click
:on-key-down on-show-teams-keydown}
[:div {:class (stl/css :team-name)}
[:img {:src (cf/resolve-team-photo-url team)
:class (stl/css :team-picture)
:alt (:name team)}]
[:span {:class (stl/css :team-text) :title (:name team)} (:name team)]]
arrow-icon]]
;; Teams Dropdown
[:> teams-selector-dropdown* {:show show-teams-menu?
:on-close close-teams-menu
:id "organizations-list"
:class (stl/css :dropdown :teams-dropdown)
:team team
:profile profile
:teams teams
:show-default-team false
:allow-create-teams false
:allow-create-org true}]]))
(mf/defc sidebar-team-switch*
[{:keys [team profile]}]
(let [nitrate? (contains? cf/flags :nitrate)
org-id (when nitrate? (:organization-id team))
teams (cond->> (mf/deref refs/teams)
nitrate?
(filter #(= (-> % val :organization-id) org-id)))
(let [teams (mf/deref refs/teams)
subscription
(get team :subscription)
@@ -672,10 +586,7 @@
:class (stl/css :dropdown :teams-dropdown)
:team team
:profile profile
:teams teams
:show-default-team true
:allow-create-teams true
:allow-create-org false}]
:teams teams}]
[:> team-options-dropdown* {:show show-team-options-menu?
:on-close close-team-options-menu
@@ -792,8 +703,6 @@
[:*
[:div {:class (stl/css-case :sidebar-content true)
:ref container}
(when (contains? cf/flags :nitrate)
[:> sidebar-org-switch* {:team team :profile profile}])
[:> sidebar-team-switch* {:team team :profile profile}]
[:> sidebar-search* {:search-term search-term

View File

@@ -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)}

View File

@@ -11,10 +11,8 @@
[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]
@@ -99,11 +97,8 @@
[:div {:class (stl/css :modal-container)}
[:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-title)} title]
[:> icon-button* {:variant "ghost"
:class (stl/css :modal-close-btn)
:icon i/close
:aria-label (tr "labels.close")
:on-click cancel-fn}]]
[:button {:class (stl/css :modal-close-btn)
:on-click cancel-fn} deprecated-icon/close]]
[:div {:class (stl/css :modal-content)}
(when (and (string? subtitle) (not= subtitle ""))
@@ -129,10 +124,14 @@
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
(when-not (= cancel-label :omit)
[:> button* {:variant "secondary"
:on-click cancel-fn}
cancel-label])
[:input {:class (stl/css :cancel-button)
:type "button"
:value cancel-label
:on-click cancel-fn}])
[:> button* {:variant (if (= accept-style :danger) "destructive" "primary")
:on-click accept-fn}
accept-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}]]]]]))

View File

@@ -33,9 +33,7 @@
}
.modal-close-btn {
position: absolute;
top: var(--sp-s);
right: var(--sp-s);
@extend .modal-close-btn-base;
}
.modal-content {
@@ -55,6 +53,17 @@
@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;
}

View File

@@ -12,7 +12,6 @@
[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*]]
@@ -64,7 +63,6 @@
:Select select*
:Switch switch*
:Checkbox checkbox*
:RadioButtons radio-buttons*
:Combobox combobox*
:Text text*
:TabSwitcher tab-switcher*

View File

@@ -15,7 +15,6 @@
display: inline-flex;
align-items: center;
justify-content: center;
column-gap: var(--sp-xs);
}

View File

@@ -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.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?}]]))]))

View File

@@ -1,97 +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 */ }
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.

View File

@@ -1,40 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "ds/_borders.scss" as *;
@use "ds/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;
}

View File

@@ -1,72 +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
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,
},
};

View File

@@ -1,49 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.layers.layer-button
(:require-macros
[app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[rumext.v2 :as mf]))
(def ^:private schema:layer-button
[:map
[:label :string]
[:description {:optional true} [:maybe :string]]
[:class {:optional true} :string]
[:expandable {:optional true} :boolean]
[:expanded {:optional true} :boolean]
[:icon {:optional true} :string]
[:on-toggle-expand fn?]])
(mf/defc layer-button*
{::mf/schema schema:layer-button}
[{:keys [label description class is-expandable expanded icon on-toggle-expand children] :rest props}]
(let [button-props (mf/spread-props props
{:class [class (stl/css-case :layer-button true
:layer-button--expandable is-expandable
:layer-button--expanded expanded)]
:type "button"
:on-click on-toggle-expand})]
[:div {:class (stl/css :layer-button-wrapper)}
[:> "button" button-props
[:div {:class (stl/css :layer-button-content)}
(when is-expandable
(if expanded
[:> icon* {:icon-id i/arrow-down :class (stl/css :folder-node-icon)}]
[:> icon* {:icon-id i/arrow-right :class (stl/css :folder-node-icon)}]))
(when icon
[:> icon* {:icon-id icon :class (stl/css :layer-button-icon)}])
[:span {:class (stl/css :layer-button-name)}
label]
(when description
[:span {:class (stl/css :layer-button-description)}
description])
[:span {:class (stl/css :layer-button-quantity)}]]]
[:div {:class (stl/css :layer-button-actions)}
children]]))

View File

@@ -1,56 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/typography.scss" as *;
@use "ds/colors.scss" as *;
.layer-button-wrapper {
--layer-button-block-size: #{$sz-32};
--layer-button-background: var(--color-background-primary);
--layer-button-text: var(--color-foreground-secondary);
display: flex;
justify-content: space-between;
block-size: var(--layer-button-block-size);
background: var(--layer-button-background);
color: var(--layer-button-text);
}
.layer-button {
@include use-typography("body-small");
appearance: none;
flex: 1;
display: flex;
align-items: center;
border: none;
background: none;
color: inherit;
}
.layer-button--expanded {
& .layer-button-name {
color: var(--color-foreground-primary);
}
}
.layer-button-content {
display: flex;
align-items: center;
gap: var(--sp-xs);
}
.layer-button-description {
padding: var(--sp-xs);
background-color: var(--color-background-tertiary);
border-radius: $br-6;
}

View File

@@ -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)

View File

@@ -19,10 +19,7 @@
[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.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.components.radio-buttons :refer [radio-button radio-buttons]]
[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]]
@@ -263,9 +260,8 @@
[:div {:class (stl/css-case :element-options true
:viewer-code-block (= :viewer from))}
[:div {:class (stl/css :attributes-block)}
[:> button* {:variant "secondary"
:class (stl/css :download-button)
:on-click handle-copy-all-code}
[:button {:class (stl/css :download-button)
:on-click handle-copy-all-code}
"Copy all code"]]
#_[:div.attributes-block
@@ -292,10 +288,10 @@
;; :options [{:label "CSS" :value "css"}]}]
[:div {:class (stl/css :action-btns)}
[:> icon-button* {:variant "ghost"
:aria-label "Expand"
:on-click on-expand
:icon i/code}]
[:button {:class (stl/css :expand-button)
:on-click on-expand}
deprecated-icon/code]
[:> copy-button* {:data copy-css-fn
:class (stl/css :css-copy-btn)
:on-copied on-style-copied}]]]
@@ -322,21 +318,21 @@
:rotated collapsed-markup?)}
deprecated-icon/arrow]]
[:> 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"}]}]
[:& 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}]]
[:div {:class (stl/css :action-btns)}
[:> icon-button* {:variant "ghost"
:aria-label "Expand"
:on-click on-expand
:icon i/code}]
[:button {:class (stl/css :expand-button)
:on-click on-expand}
deprecated-icon/code]
[:> copy-button* {:data copy-html-fn
:class (stl/css :html-copy-btn)
:on-copied on-markup-copied}]]]

View File

@@ -17,18 +17,16 @@
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 {
margin: var(--sp-s) 0;
@extend .button-secondary;
@include deprecated.uppercaseTitleTipography;
height: deprecated.$s-32;
width: 100%;
margin: deprecated.$s-8 0;
}
.code-block {
@@ -75,6 +73,7 @@
gap: deprecated.$s-4;
}
.expand-button,
.css-copy-btn,
.html-copy-btn {
@extend .button-tertiary;
@@ -86,6 +85,9 @@
}
}
.code-lang-options {
max-width: deprecated.$s-108;
}
.code-lang-select {
@include deprecated.uppercaseTitleTipography;
width: deprecated.$s-72;

View File

@@ -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

View File

@@ -27,8 +27,9 @@
[okulary.core :as l]
[rumext.v2 :as mf]))
(mf/defc comments-menu*
{::mf/memo true}
(mf/defc comments-menu
{::mf/props :obj
::mf/memo true}
[]
(let [state (mf/deref refs/comments-local)
cmode (:mode state)

View File

@@ -14,13 +14,10 @@
[app.main.data.viewer.shortcuts :as sc]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[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.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]]
@@ -36,12 +33,20 @@
[]
(modal/show! :login-register {}))
(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*)
(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}]
(let [open* (mf/use-state false)
open? (deref open*)
open-dropdown
(mf/use-fn
(fn [event]
@@ -70,7 +75,7 @@
[:div {:class (stl/css-case :zoom-widget true
:selected open?)
:on-click (if open? close-dropdown open-dropdown)
:on-click open-dropdown
:title (tr "workspace.header.zoom")}
[:span {:class (stl/css :label)} (fmt/format-percent zoom)]
[:& dropdown {:show open?
@@ -78,18 +83,18 @@
[:ul {:class (stl/css :dropdown)}
[:li {:class (stl/css :basic-zoom-bar)}
[:span {:class (stl/css :zoom-btns)}
[:> icon-button* {:variant "ghost"
:aria-label (tr "shortcuts.decrease-zoom")
:on-click on-decrease
:icon i/remove}]
[:p {:class (stl/css :zoom-text)}
[: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)}
(fmt/format-percent zoom)]
[:> 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}
[: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}
(tr "workspace.header.reset-zoom")]]
[:li {:class (stl/css :zoom-option)
@@ -114,7 +119,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)
@@ -154,7 +159,6 @@
handle-zoom-fit
(mf/use-fn
#(st/emit! dv/zoom-to-fit))]
(mf/with-effect [permissions share]
(when (and
(:in-team permissions)
@@ -163,7 +167,7 @@
(open-share-dialog)))
[:div {:class (stl/css :options-zone)}
[:> progress-widget*]
[:& progress-widget]
(case section
:interactions [:*
@@ -171,41 +175,40 @@
[:> 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)
[:> icon-button* {:variant "ghost"
:aria-label (tr "viewer.header.edit-in-workspace")
:on-click go-to-workspace
:icon i/curve}])
[:span {:on-click go-to-workspace
:class (stl/css :edit-btn)}
deprecated-icon/curve])
[:> icon-button* {:variant "ghost"
:aria-pressed fullscreen?
:aria-label (tr "viewer.header.fullscreen")
:on-click toggle-fullscreen
:icon i/expand}]
[:span {:title (tr "viewer.header.fullscreen")
:class (stl/css-case :fullscreen-btn true
:selected fullscreen?)
:on-click toggle-fullscreen}
deprecated-icon/expand]
(when (:in-team permissions)
[:> button* {:variant "primary"
:class (stl/css :share-btn)
:on-click open-share-dialog}
[:button {:on-click open-share-dialog
:class (stl/css :share-btn)}
(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]}]
(mf/defc header-sitemap
[{:keys [project file page frame toggle-thumbnails] :as props}]
(let [project-name (:name project)
file-name (:name file)
page-name (:name page)
@@ -314,44 +317,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)}
[:> 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}]
[: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]
(when (or (:in-team permissions)
(= (:who-comment permissions) "all"))
[:> 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}])
[: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])
(when (or (:in-team permissions)
(and (= (:type permissions) :share-link)
(= (:who-inspect permissions) "all")))
[:> 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}])]
[: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])]
[:> 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}]]))

View File

@@ -12,7 +12,7 @@
grid-column: 1 / span 1;
grid-row: 1 / span 1;
display: grid;
grid-template-columns: 1fr auto 1fr;
grid-template-columns: 1fr deprecated.$s-92 1fr;
justify-content: space-between;
align-items: center;
height: deprecated.$s-48;
@@ -130,9 +130,23 @@
// SECTION BUTTONS
.mode-zone {
display: flex;
flex-direction: row;
gap: var(--sp-xs);
@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;
}
// OPTION AREA
@@ -151,8 +165,33 @@
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 {
margin-left: var(--sp-xs);
@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);
}
}
.go-log-btn {
@@ -206,15 +245,43 @@
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-48;
min-width: deprecated.$s-64;
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 {

View File

@@ -20,9 +20,6 @@
[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]
@@ -174,11 +171,10 @@
[:div {:class (stl/css :share-link-header)}
[:h2 {:class (stl/css :share-link-title)}
(tr "common.share-link.title")]
[:> icon-button* {:variant "ghost"
:class (stl/css :modal-close-button)
:aria-label (tr "labels.close")
:on-click on-close
:icon i/close}]]
[:button {:class (stl/css :modal-close-button)
:on-click on-close
:title (tr "labels.close")}
deprecated-icon/close]]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :share-link-section)}
(when (and (not confirm?) (some? current-link))
@@ -189,10 +185,10 @@
:placeholder (tr "common.share-link.placeholder")
:read-only true}]
[:> icon-button* {:variant "ghost"
:aria-label (tr "viewer.header.share.copy-link")
:on-click copy-link
:icon i/clipboard}]])
[:button {:class (stl/css :copy-button)
:title (tr "viewer.header.share.copy-link")
:on-click copy-link}
deprecated-icon/clipboard]])
[:div {:class (stl/css :hint-wrapper)}
(when (not ^boolean confirm?)
@@ -203,22 +199,28 @@
[:div {:class (stl/css :description)}
(tr "common.share-link.confirm-deletion-link-description")]
[:div {:class (stl/css :actions)}
[:> button* {:variant "secondary"
:on-click #(reset! confirm* false)}
(tr "labels.cancel")]
[:> button* {:variant "destructive"
:on-click delete-link}
(tr "common.share-link.destroy-link")]]]
[: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")}]]]
(some? current-link)
[:> button* {:variant "destructive"
:on-click try-delete-link}
(tr "common.share-link.destroy-link")]
[:input
{:type "button"
:class (stl/css :button-danger)
:on-click try-delete-link
:value (tr "common.share-link.destroy-link")}]
:else
[:> button* {:variant "primary"
:on-click create-link}
(tr "common.share-link.get-link")])]]
[:input
{:type "button"
:class (stl/css :button-active)
:on-click create-link
:value (tr "common.share-link.get-link")}])]]
(when (not ^boolean confirm?)
@@ -303,7 +305,6 @@
: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")]
@@ -314,3 +315,6 @@
:options [{:value "team" :label (tr "common.share-link.team-members")}
{:value "all" :label (tr "common.share-link.all-users")}]
:on-change on-inspect-change}]]]])])]]]))

View File

@@ -30,9 +30,7 @@
}
.modal-close-button {
position: absolute;
top: var(--sp-s);
right: var(--sp-s);
@extend .modal-close-btn-base;
}
.modal-content {
@@ -76,6 +74,18 @@
}
}
.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);
@@ -87,6 +97,18 @@
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;

View File

@@ -36,7 +36,6 @@
[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*]]

View File

@@ -25,11 +25,10 @@
[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]
@@ -416,25 +415,24 @@
: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"
: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}]}])]
[:& 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"}]])]
(when (and (not= selected-mode :image)
(= color-style :direct-color))
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.colorpicker.get-color")
:aria-pressed picking-color?
:on-click handle-click-picker
:icon i/picker}])
[:button {:class (stl/css-case :picker-btn true
:selected picking-color?)
:on-click handle-click-picker}
deprecated-icon/picker])
(when (= color-style :token-color)
[:div {:class (stl/css :token-color-title)}
@@ -485,11 +483,12 @@
: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)}

View File

@@ -46,6 +46,52 @@
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;

View File

@@ -152,6 +152,7 @@
(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")

View File

@@ -30,7 +30,6 @@
[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*]]
@@ -45,6 +44,12 @@
[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]
@@ -163,10 +168,12 @@
[:div {:class (stl/css :sample-library-item)
:key (dm/str id)}
[:div {:class (stl/css :sample-library-item-name)} (:name library)]
[:> button* {:variant "secondary"
:on-click import-library
:disabled (some? importing?)}
(if (= importing? id) (tr "labels.adding") (tr "labels.add"))]]))
[: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}]]))
(defn- empty-library?
"Check if currentt library summary has elements or not"
@@ -315,12 +322,14 @@
[:> library-description* {:summary summary}]]]
(if ^boolean is-shared
[:> button* {:variant "secondary"
:on-click unpublish}
(tr "common.unpublish")]
[:> button* {:variant "primary"
:on-click publish}
(tr "common.publish")])]
[: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}])]
(for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)]
@@ -368,11 +377,12 @@
(let [summary (-> (:library-summary library)
(adapt-backend-summary))]
[:> library-description* {:summary summary}])]]
[:> 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}]])]
[: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]])]
(when (empty? shared-libraries)
[:div {:class (stl/css :section-list-empty)}
@@ -637,13 +647,11 @@
:on-click close-dialog-outside
:data-testid "libraries-modal"}
[:div {:class (stl/css :modal-dialog)}
[:> 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}]
[:button {:class (stl/css :close-btn)
:on-click close-dialog
:aria-label (tr "labels.close")
:data-testid "close-libraries"}
close-icon]
[:div {:class (stl/css :modal-title)}
(tr "workspace.libraries.libraries")]

View File

@@ -33,7 +33,7 @@
background-color: var(--modal-background-color);
border: $b-2 solid var(--modal-border-color);
display: grid;
grid-template-rows: 0 auto 1fr;
grid-template-rows: auto 1fr;
min-width: $sz-364;
min-height: $sz-192;
height: $sz-520;
@@ -42,10 +42,21 @@
max-width: $sz-712;
}
// TODO: Remove this extended creating modal component
.close-btn {
position: absolute;
top: var(--sp-s);
right: var(--sp-s);
@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);
}
.modal-title {
@@ -109,6 +120,46 @@
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);
}
@@ -119,6 +170,26 @@
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;
@@ -357,3 +428,24 @@
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;
}

View File

@@ -853,9 +853,8 @@
[:*
[:> icon-button* {:variant "ghost"
:aria-pressed show-menu?
:aria-label (tr "shortcut-subsection.main-menu")
:on-click (if show-menu? close-all-menus open-menu)
:on-click open-menu
:icon i/menu}]
[:> dropdown-menu* {:show show-menu?

View File

@@ -18,10 +18,9 @@
[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]]
@@ -179,27 +178,27 @@
[:ul {:class (dm/str size-classname " " (stl/css-case :palette-btn-list true
:hidden-bts hide-palettes?))}
[:li {:class (stl/css :palette-item)}
[:> 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}]]
[: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]]
[:li {:class (stl/css :palette-item)}
[:> 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}]]]
[: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]]]
(if any-palette?
[:*
[: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}]]
[:button {:class (stl/css :palette-actions)
:on-click #(swap! state* update :show-menu not)}
deprecated-icon/menu]
[:div {:class (stl/css :palette)
:ref container}
(when text-palette?

View File

@@ -49,6 +49,7 @@
&.wide {
width: 100%;
}
.resize-area {
grid-area: resize;
height: deprecated.$s-8;
@@ -71,22 +72,49 @@
&.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 {

View File

@@ -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)

View File

@@ -20,19 +20,23 @@
[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.workspace.presence :refer [active-sessions*]]
[app.main.ui.icons :as deprecated-icon]
[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]}]
@@ -68,12 +72,11 @@
zoom (fmt/format-percent zoom {:precision 0})]
[:*
[:div {:on-click (if open? close-dropdown open-dropdown)
[:div {:on-click 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)}
@@ -87,10 +90,9 @@
:aria-label (tr "shortcuts.increase-zoom")
:on-click on-increase
:icon i/add}]]
[:> button* {:variant "ghost"
:on-click on-zoom-reset}
[:button {:class (stl/css :reset-btn)
: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")
@@ -98,7 +100,6 @@
(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")
@@ -197,43 +198,51 @@
[: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-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)}])]
[: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)}])]]
(when-not ^boolean read-only?
[:> icon-button* {:variant "ghost"
:aria-pressed (contains? layout :document-history)
:aria-label (tr "workspace.sidebar.history")
:on-click toggle-history
:icon i/history}])
[: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]])
(when display-share-button?
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.header.share")
:on-click open-share-dialog
:icon i/to-corner}])
[: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.viewer" (sc/get-tooltip :open-viewer))
:on-click nav-to-viewer
:icon i/play}]]))

View File

@@ -11,8 +11,8 @@
justify-content: space-between;
align-items: center;
min-width: deprecated.$s-256;
padding: deprecated.$s-8 deprecated.$s-12;
gap: deprecated.$s-4;
padding: deprecated.$s-8;
gap: deprecated.$s-8;
background-color: var(--panel-background-color);
}
@@ -28,14 +28,19 @@
}
.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;
height: deprecated.$s-32;
border-radius: deprecated.$br-8;
.label {
@include deprecated.bodySmallTypography;
height: 100%;
padding: deprecated.$s-8 0;
color: var(--button-tertiary-foreground-color-rest);
}
@@ -79,6 +84,13 @@
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;
@@ -101,11 +113,127 @@
}
}
.comments-button-wrapper {
position: relative;
.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-unread {
.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 {
position: absolute;
width: 8px;
height: 8px;

View File

@@ -17,9 +17,8 @@
[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.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon]
[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]
@@ -162,40 +161,43 @@
: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* {:variant "primary"
:on-click show-libraries-dialog
:data-testid "libraries"}
[:button {:class (stl/css :add-library-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
(tr "workspace.assets.add-library")]
[:> button* {:variant "secondary"
:on-click show-libraries-dialog
:data-testid "libraries"}
[:button {:class (stl/css :libraries-button)
: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")}
[:> 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}]]
[: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]]
[:> 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")

View File

@@ -17,14 +17,89 @@
padding-top: deprecated.$s-8;
}
.assets-header {
display: flex;
flex-direction: column;
gap: var(--sp-xxs);
.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);
}
}
.filter-button {
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
.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;
}
}
.sections-container {
@@ -50,6 +125,10 @@
border-radius: deprecated.$br-8;
}
.section-btn {
@include deprecated.buttonStyle;
}
.assets-header {
padding: 0 0 deprecated.$s-24 deprecated.$s-12;
}

View File

@@ -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.file-uploader :refer [file-uploader]]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[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"
: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"}]}]])
[:& 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"}]]])
(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

View File

@@ -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,17 +113,18 @@
: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)}])]))

View File

@@ -291,12 +291,13 @@
:value current-search
:on-clear clear-search-text
:placeholder (tr "workspace.sidebar.layers.search")}
[:> 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}]]
[: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 "ghost"
:aria-label (tr "labels.close")
:on-click toggle-search

View File

@@ -19,7 +19,39 @@
padding: 0 deprecated.$s-12 0 deprecated.$s-8;
gap: deprecated.$s-4;
.filter-button {
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
@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;
}
}
}
}
@@ -111,7 +143,7 @@
.filters-container {
@extend .menu-dropdown;
position: absolute;
left: deprecated.$s-16;
left: deprecated.$s-20;
width: deprecated.$s-192;
.filter-menu-item {
@include deprecated.bodySmallTypography;

View File

@@ -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.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.components.radio-buttons :refer [radio-button 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* {: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"}]}]]))
[:& 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"}]]]))

View File

@@ -10,8 +10,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc]
[app.main.store :as st]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
@@ -43,59 +42,68 @@
(when-not (and disabled-align disabled-distribute)
[:div {:class (stl/css :align-options)}
[:div {:class (stl/css :align-group-horizontal)}
[:> 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.hleft" (sc/get-tooltip :align-left))
:data-value "hleft"
:on-click align-objects}
deprecated-icon/align-left]
[:> 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.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-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-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/distribute-horizontally
:aria-label (tr "workspace.align.hdistribute" (sc/get-tooltip :h-distribute))
:on-click distribute-objects
:data-value "horizontal"
:disabled disabled-distribute}]]
[: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]]
[:div {:class (stl/css :align-group-vertical)}
[:> 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.vtop" (sc/get-tooltip :align-top))
:data-value "vtop"
:on-click align-objects}
deprecated-icon/align-top]
[:> 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.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-bottom
:aria-label (tr "workspace.align.vbottom" (sc/get-tooltip :align-bottom))
:on-click align-objects
:data-value "vbottom"
: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/distribute-vertical-spacing
:aria-label (tr "workspace.align.vdistribute" (sc/get-tooltip :v-distribute))
:on-click distribute-objects
:data-value "vertical"
:disabled disabled-distribute}]]])))

View File

@@ -4,10 +4,12 @@
//
// 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 {
@@ -24,3 +26,27 @@
.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);
}
}
}
}

View File

@@ -15,6 +15,7 @@
[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]))
@@ -102,12 +103,10 @@
[:div {:class (stl/css-case :first-row true
:hidden hidden?)}
[:div {:class (stl/css :blur-info)}
[:> 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}]
[:button {:class (stl/css-case :show-more true
:selected more-options?)
:on-click toggle-more-options}
deprecated-icon/menu]
[:span {:class (stl/css :label)}
(tr "workspace.options.blur-options.title")]]
[:div {:class (stl/css :actions)}

View File

@@ -37,7 +37,21 @@
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;

View File

@@ -15,12 +15,15 @@
[app.main.data.workspace.shortcuts :as sc]
[app.main.features :as features]
[app.main.store :as st]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.components.radio-buttons :refer [radio-button 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)
@@ -67,40 +70,41 @@
(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 :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}]}]]
[: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"}]]]
[:> icon-button* {:variant "ghost"
:icon i/boolean-flatten
:aria-label (tr "workspace.shape.menu.flatten")
:on-click flatten-objects
:disabled disabled-flatten}]])))
[: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]])))

View File

@@ -4,18 +4,45 @@
//
// 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);
}
.boolean-group {
.bool-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);
}

View File

@@ -145,9 +145,9 @@
:on-change on-radius-r3-change
:value (:r3 values)}]]])
[:> icon-button* {:variant "ghost"
[:> icon-button* {:class (stl/css-case :selected radius-expanded)
: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"))

View File

@@ -28,6 +28,12 @@
@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;
}

View File

@@ -13,7 +13,6 @@
[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]))
@@ -50,16 +49,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."
@@ -218,8 +217,8 @@
:origin :color-selection
:on-close on-close}]))
(when (and (false? @expand-lib-color) (< 3 (count library-colors)))
[:> button* {:variant "secondary"
:on-click #(reset! expand-lib-color true)}
[:button {:class (stl/css :more-colors-btn)
:on-click #(reset! expand-lib-color true)}
(tr "workspace.options.more-lib-colors")])]
[:div {:class (stl/css :selected-color-group)}
@@ -236,8 +235,8 @@
:on-close on-close}])
(when (and (false? @expand-color) (< 3 (count colors)))
[:> button* {:variant "secondary"
:on-click #(reset! expand-color true)}
[:button {:class (stl/css :more-colors-btn)
:on-click #(reset! expand-color true)}
(tr "workspace.options.more-colors")])]
[:div {:class (stl/css :selected-color-group)}
@@ -260,6 +259,6 @@
(when (and (false? @expand-token-color)
(< 3 (count token-colors)))
[:> button* {:variant "secondary"
:on-click #(reset! expand-token-color true)}
[:button {:class (stl/css :more-colors-btn)
:on-click #(reset! expand-token-color true)}
(tr "workspace.options.more-token-colors")])]])]))

View File

@@ -40,5 +40,7 @@
}
.more-colors-btn {
justify-content: center;
@extend .button-secondary;
@include deprecated.uppercaseTitleTipography;
height: deprecated.$s-32;
}

View File

@@ -28,6 +28,7 @@
[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]]
@@ -36,7 +37,6 @@
[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,17 +794,15 @@
[: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"
: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"}]}]]
[:& 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"}]]]
(when-not (or search? (str/empty? (:path filters)))
[:button {:class (stl/css :swap-library-back)
@@ -899,13 +897,11 @@
(when menu-entries?
[:div {:class (stl/css :pill-actions)}
[:> 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}]
[:button {:class (stl/css-case :pill-actions-btn true
:selected menu-open?)
:on-click on-menu-click}
[:> icon* {:icon-id i/menu}]]
[:& dropdown {:show menu-open?
:on-close on-menu-close}
[:ul {:class (stl/css-case :pill-actions-dropdown true

View File

@@ -587,9 +587,14 @@
}
.pill-actions-btn {
@extend .button-secondary;
cursor: unset;
block-size: 100%;
inline-size: 100%;
border-radius: 0 $br-8 $br-8 0;
&.extended {
block-size: $sz-48;
&.selected {
@extend .button-icon-selected;
}
}

View File

@@ -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.ds.controls.checkbox :refer [checkbox*]]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str]
@@ -61,7 +61,6 @@
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
@@ -219,8 +218,16 @@
:options options-v
:on-change on-constraint-v-select-changed}]]
(when first-level?
[:> 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}])]])])))
[: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}]]])]])])))

View File

@@ -137,4 +137,36 @@
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);
}
}
}
}

View File

@@ -14,7 +14,6 @@
[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]
@@ -264,10 +263,12 @@
:icon i/remove}]])])
(when (or (= :multiple exports) (seq exports))
[:> button* {:variant "secondary"
:class (stl/css :export-btn)
:on-click (when-not in-progress? on-download)
:disabled in-progress?}
[:button
{:on-click (when-not in-progress? on-download)
:class (stl/css-case
:export-btn true
:btn-disabled in-progress?)
:disabled in-progress?}
(if in-progress?
(tr "workspace.options.exporting-object")
(tr "workspace.options.export-object" (c (count shapes-with-exports))))])])]))

View File

@@ -27,7 +27,7 @@
.multiple-exports {
@include deprecated.flexRow;
grid-column: 1 / span 8;
grid-column: 1 / span 9;
.label {
@extend .mixed-bar;
}
@@ -76,6 +76,8 @@
}
.export-btn {
grid-column: 1 / span 8;
justify-content: center;
@extend .button-secondary;
@include deprecated.uppercaseTitleTipography;
grid-column: 1 / span 9;
height: deprecated.$s-32;
}

View File

@@ -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,9 +252,16 @@
(when (or (= type :frame)
(and (= type :multiple)
(some? hide-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}])])]))
[: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}]]])])]))

View File

@@ -50,5 +50,14 @@
}
.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);
}
}
}

View File

@@ -148,13 +148,10 @@
[:div {:class (stl/css :grid-title)}
[:div {:class (stl/css-case :option-row true
:hidden is-hidden?)}
[:> 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?}]
[:button {:class (stl/css-case :show-options true
:selected open?)
:on-click toggle-advanced-options}
deprecated-icon/menu]
[:div {:class (stl/css :type-select-wrapper)}
[:& select
{:class (stl/css :grid-type-select)
@@ -207,11 +204,10 @@
:origin :guides
:on-change handle-change-color
:on-detach handle-detach-color}]
[:> icon-button* {:variant "ghost"
:icon i/menu
:aria-pressed show-more-options?
:aria-label (tr "labels.options")
:on-click toggle-more-options}]]
[:button {:class (stl/css-case :show-more-options true
:selected show-more-options?)
:on-click toggle-more-options}
deprecated-icon/menu]]
(when show-more-options?
[:div {:class (stl/css :second-row)}
[:button {:class (stl/css-case :btn-options true
@@ -288,12 +284,11 @@
:className (stl/css :numeric-input)
:value (or (:margin params) 0)}]]
[:> 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}]
[:button {:class (stl/css-case :show-more-options true
:selected show-more-options?)
:on-click toggle-more-options
:disabled is-default}
deprecated-icon/menu]
(when show-more-options?
[:div {:class (stl/css :more-options)}
[:button {:class (stl/css :option-btn)

View File

@@ -38,7 +38,18 @@
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;
@@ -97,6 +108,7 @@
&.hidden {
.show-options {
@include deprecated.hiddenElement;
border: deprecated.$s-1 solid var(--input-border-color-disabled);
}
.type-select-wrapper,
@@ -164,7 +176,17 @@
.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;

View File

@@ -16,9 +16,8 @@
[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]
@@ -36,10 +35,10 @@
:justify-self
:area-name])
(mf/defc set-self-alignment*
[{:keys [is-col alignment set-alignment]}]
(mf/defc set-self-alignment
[{:keys [is-col? alignment set-alignment] :as props}]
(let [alignment (or alignment :auto)
type (if is-col "col" "row")
type (if is-col? "col" "row")
handle-set-alignment
(mf/use-callback
@@ -47,35 +46,39 @@
(fn [value]
(set-alignment (-> value keyword))))]
[:> radio-buttons* {:class (stl/css :self-align-menu)
:selected (d/name alignment)
:name (dm/str "flex-align-items-" type)
:allow-empty true
[:div {:class (stl/css :self-align-menu)}
[:& radio-buttons {:selected (d/name alignment)
: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"}]}]))
: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)}]]]))
(mf/defc options
{::mf/wrap [mf/memo]}
@@ -179,19 +182,16 @@
(when open?
[:div {:class (stl/css :grid-cell-menu-container)}
[:> radio-buttons* {:selected (d/name cell-mode)
:name "cell-mode"
[:div {:class (stl/css :cell-mode :row)}
[:& radio-buttons {:selected (d/name cell-mode)
: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?)}]}]
: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?)}]]]
(when (= :area cell-mode)
[:div {:class (stl/css :row)}
@@ -261,15 +261,16 @@
: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* {:variant "secondary"
:class (stl/css :edit-grid-btn)
:on-click toggle-edit-mode}
(tr "workspace.layout-grid.editor.options.edit-grid")]]])]))
[: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")]]])]))

View File

@@ -30,6 +30,17 @@
@include deprecated.flexRow;
}
.cell-mode :global(label) {
padding: 0 deprecated.$s-12;
}
.edit-grid-btn {
@extend .button-secondary;
@include deprecated.uppercaseTitleTipography;
width: 100%;
padding: deprecated.$s-8;
}
.area-input {
@extend .input-element;
@include deprecated.bodySmallTypography;
@@ -55,7 +66,3 @@
border-radius: 0 deprecated.$br-8 deprecated.$br-8 0;
border-left: deprecated.$s-1 solid var(--panel-background-color);
}
.edit-grid-btn {
flex-grow: 1;
}

View File

@@ -17,13 +17,13 @@
[app.main.data.workspace.interactions :as dwi]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.components.select :refer [select]]
[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.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.foundations.assets.icon :as i]
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
[app.util.dom :as dom]
@@ -582,43 +582,41 @@
:options animation-opts
:on-change change-animation-type}]]]
;; Way
;; Direction
(when (ctsi/has-way? interaction)
[:div {:class (stl/css :interaction-row)}
[:div {:class (stl/css :interaction-row-radio)}
[:> radio-buttons* {:selected (d/name way)
:on-change change-way
:name "animation-way"
:options [{:id "animation-way-in"
:label (tr "workspace.options.interaction-animation-direction-in")
:value "in"}
{:id "animation-way-out"
:label (tr "workspace.options.interaction-animation-direction-out")
:value "out"}]}]]])
[:& radio-buttons {:selected (d/name way)
:on-change change-way
:name "animation-way"}
[:& radio-button {:value "in"
:id "animation-way-in"}]
[:& radio-button {:id "animation-way-out"
:value "out"}]]]])
;; Direction
(when (ctsi/has-direction? interaction)
[:div {:class (stl/css :interaction-row)}
[:div {:class (stl/css :interaction-row-radio)}
[:> radio-buttons* {:selected (d/name direction)
:on-change change-direction
:name "animation-direction"
:options [{:id "animation-right"
:icon i/row
:label (tr "workspace.options.interaction-animation-direction-right")
:value "right"}
{:id "animation-left"
:icon i/row-reverse
:label (tr "workspace.options.interaction-animation-direction-left")
:value "left"}
{:id "animation-down"
:icon i/column
:label (tr "workspace.options.interaction-animation-direction-down")
:value "down"}
{:id "animation-up"
:icon i/column-reverse
:label (tr "workspace.options.interaction-animation-direction-up")
:value "up"}]}]]])
[:& radio-buttons {:selected (d/name direction)
:on-change change-direction
:name "animation-direction"}
[:& radio-button {:icon i/row
:icon-class (stl/css :right)
:value "right"
:id "animation-right"}]
[:& radio-button {:icon i/row-reverse
:icon-class (stl/css :left)
:id "animation-left"
:value "left"}]
[:& radio-button {:icon i/column
:icon-class (stl/css :down)
:id "animation-down"
:value "down"}]
[:& radio-button {:icon i/column-reverse
:icon-class (stl/css :up)
:id "animation-up"
:value "up"}]]]])
;; Duration
(when (ctsi/has-duration? interaction)

View File

@@ -23,13 +23,12 @@
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.numeric-input :as deprecated-input]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.context :as muc]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.formats :as fmt]
[app.main.ui.hooks :as h]
@@ -203,118 +202,128 @@
:space-between i/align-content-row-between
:stretch i/align-content-row-stretch))))
(mf/defc direction-row-flex*
{::mf/private true}
(mf/defc direction-row-flex
{::mf/props :obj
::mf/private true}
[{:keys [value on-change]}]
[:> radio-buttons* {:class (stl/css :direction-row-flex)
:selected (d/name value)
:on-change on-change
:name "flex-direction"
:options [{:id "flex-direction-row"
:icon (dir-icons-refactor :row)
:label "Row"
:value "row"}
{:id "flex-direction-row-reverse"
:icon (dir-icons-refactor :row-reverse)
:label "Row reverse"
:value "row-reverse"}
{:id "flex-direction-column"
:icon (dir-icons-refactor :column)
:label "Column"
:value "column"}
{:id "flex-direction-column-reverse"
:icon (dir-icons-refactor :column-reverse)
:label "Column reverse"
:value "column-reverse"}]}])
[:& radio-buttons {:class (stl/css :direction-row-flex)
:selected (d/name value)
:decode-fn keyword
:on-change on-change
:name "flex-direction"}
[:& radio-button {:value "row"
:id "flex-direction-row"
:title "Row"
:icon (dir-icons-refactor :row)}]
[:& radio-button {:value "row-reverse"
:id "flex-direction-row-reverse"
:title "Row reverse"
:icon (dir-icons-refactor :row-reverse)}]
[:& radio-button {:value "column"
:id "flex-direction-column"
:title "Column"
:icon (dir-icons-refactor :column)}]
[:& radio-button {:value "column-reverse"
:id "flex-direction-column-reverse"
:title "Column reverse"
:icon (dir-icons-refactor :column-reverse)}]])
(mf/defc wrap-row*
(mf/defc wrap-row
{::mf/props :obj}
[{:keys [wrap-type on-click]}]
[:> icon-button* {:variant "ghost"
:aria-label (if (= :wrap wrap-type) "No wrap" "Wrap")
:aria-pressed (= wrap-type :wrap)
:on-click on-click
:icon i/wrap}])
[:button {:class (stl/css-case :wrap-button true
:selected (= wrap-type :wrap))
:title (if (= :wrap wrap-type)
"No wrap"
"Wrap")
:on-click on-click}
deprecated-icon/wrap])
(mf/defc align-row*
(mf/defc align-row
{::mf/props :obj}
[{:keys [is-column value on-change]}]
[:> radio-buttons* {:class (stl/css :align-row)
:selected (d/name value)
:on-change on-change
:name "flex-align-items"
:options [{:id "align-items-start"
:icon (get-layout-flex-icon :align-items :start is-column)
:label "Align items start"
:value "start"}
{:id "align-items-center"
:icon (get-layout-flex-icon :align-items :center is-column)
:label "Align items center"
:value "center"}
{:id "align-items-end"
:icon (get-layout-flex-icon :align-items :end is-column)
:label "Align items end"
:value "end"}]}])
[:& radio-buttons {:class (stl/css :align-row)
:selected (d/name value)
:decode-fn keyword
:on-change on-change
:name "flex-align-items"}
[:& radio-button {:value "start"
:icon (get-layout-flex-icon :align-items :start is-column)
:title "Align items start"
:id "align-items-start"}]
[:& radio-button {:value "center"
:icon (get-layout-flex-icon :align-items :center is-column)
:title "Align items center"
:id "align-items-center"}]
[:& radio-button {:value "end"
:icon (get-layout-flex-icon :align-items :end is-column)
:title "Align items end"
:id "align-items-end"}]])
(mf/defc align-content-row*
(mf/defc align-content-row
{::mf/props :obj}
[{:keys [is-column value on-change]}]
[:> radio-buttons* {:class (stl/css :align-content-row)
:selected (d/name value)
:on-change on-change
:name "flex-align-content"
:options [{:id "align-content-start"
:icon (get-layout-flex-icon :align-content :start is-column)
:label "Align content start"
:value "start"}
{:id "align-content-center"
:icon (get-layout-flex-icon :align-content :center is-column)
:label "Align content center"
:value "center"}
{:id "align-content-end"
:icon (get-layout-flex-icon :align-content :end is-column)
:label "Align content end"
:value "end"}
{:id "align-content-space-between"
:icon (get-layout-flex-icon :align-content :space-between is-column)
:label "Align content space-between"
:value "space-between"}
{:id "align-content-space-around"
:icon (get-layout-flex-icon :align-content :space-around is-column)
:label "Align content space-around"
:value "space-around"}
{:id "align-content-space-evenly"
:icon (get-layout-flex-icon :align-content :space-evenly is-column)
:label "Align content space-evenly"
:value "space-evenly"}]}])
[:& radio-buttons {:class (stl/css :align-content-row)
:selected (d/name value)
:decode-fn keyword
:on-change on-change
:name "flex-align-content"}
[:& radio-button {:value "start"
:icon (get-layout-flex-icon :align-content :start is-column)
:title "Align content start"
:id "align-content-start"}]
[:& radio-button {:value "center"
:icon (get-layout-flex-icon :align-content :center is-column)
:title "Align content center"
:id "align-content-center"}]
[:& radio-button {:value "end"
:icon (get-layout-flex-icon :align-content :end is-column)
:title "Align content end"
:id "align-content-end"}]
[:& radio-button {:value "space-between"
:icon (get-layout-flex-icon :align-content :space-between is-column)
:title "Align content space-between"
:id "align-content-space-between"}]
[:& radio-button {:value "space-around"
:icon (get-layout-flex-icon :align-content :space-around is-column)
:title "Align content space-around"
:id "align-content-space-around"}]
[:& radio-button {:value "space-evenly"
:icon (get-layout-flex-icon :align-content :space-evenly is-column)
:title "Align content space-evenly"
:id "align-content-space-evenly"}]])
(mf/defc justify-content-row*
(mf/defc justify-content-row
{::mf/props :obj}
[{:keys [is-column justify-content on-change]}]
[:> radio-buttons* {:class (stl/css :justify-content-row)
:selected (d/name justify-content)
:on-change on-change
:name "flex-justify"
:options [{:id "justify-content-start"
:icon (get-layout-flex-icon :justify-content :start is-column)
:label "Justify content start"
:value "start"}
{:id "justify-content-center"
:icon (get-layout-flex-icon :justify-content :center is-column)
:label "Justify content center"
:value "center"}
{:id "justify-content-end"
:icon (get-layout-flex-icon :justify-content :end is-column)
:label "Justify content end"
:value "end"}
{:id "justify-content-space-between"
:icon (get-layout-flex-icon :justify-content :space-between is-column)
:label "Justify content space-between"
:value "space-between"}
{:id "justify-content-space-around"
:icon (get-layout-flex-icon :justify-content :space-around is-column)
:label "Justify content space-around"
:value "space-around"}
{:id "justify-content-space-evenly"
:icon (get-layout-flex-icon :justify-content :space-evenly is-column)
:label "Justify content space-evenly"
:value "space-evenly"}]}])
[:& radio-buttons {:class (stl/css :justify-content-row)
:selected (d/name justify-content)
:on-change on-change
:name "flex-justify"}
[:& radio-button {:value "start"
:icon (get-layout-flex-icon :justify-content :start is-column)
:title "Justify content start"
:id "justify-content-start"}]
[:& radio-button {:value "center"
:icon (get-layout-flex-icon :justify-content :center is-column)
:title "Justify content center"
:id "justify-content-center"}]
[:& radio-button {:value "end"
:icon (get-layout-flex-icon :justify-content :end is-column)
:title "Justify content end"
:id "justify-content-end"}]
[:& radio-button {:value "space-between"
:icon (get-layout-flex-icon :justify-content :space-between is-column)
:title "Justify content space-between"
:id "justify-content-space-between"}]
[:& radio-button {:value "space-around"
:icon (get-layout-flex-icon :justify-content :space-around is-column)
:title "Justify content space-around"
:id "justify-content-space-around"}]
[:& radio-button {:value "space-evenly"
:icon (get-layout-flex-icon :justify-content :space-evenly is-column)
:title "Justify content space-evenly"
:id "justify-content-space-evenly"}]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PADDING
@@ -416,19 +425,19 @@
:icon i/padding-top-bottom
:min 0
:name :p1
:property (tr "workspace.layout-grid.editor.padding.vertical")
:property (tr "workspace.layout_grid.editor.padding.vertical")
:nillable true
:applied-tokens {:p1 applied-to-p1}
:values {:p1 p1}}]
[:div {:class (stl/css :padding-simple)
:title (tr "workspace.layout-grid.editor.padding.vertical")}
:title (tr "workspace.layout_grid.editor.padding.vertical")}
[:span {:class (stl/css :icon)}
deprecated-icon/padding-top-bottom]
[:> deprecated-input/numeric-input*
{:class (stl/css :numeric-input)
:placeholder (tr "settings.multiple")
:aria-label (tr "workspace.layout-grid.editor.padding.vertical")
:aria-label (tr "workspace.layout_grid.editor.padding.vertical")
:on-change on-p1-change
:on-focus on-focus-p1
:on-blur on-padding-blur
@@ -446,19 +455,19 @@
:min 0
:name :p2
:align :right
:property (tr "workspace.layout-grid.editor.padding.horizontal")
:property (tr "workspace.layout_grid.editor.padding.horizontal")
:nillable true
:applied-tokens {:p2 applied-to-p2}
:values {:p2 p2}}]
[:div {:class (stl/css :padding-simple)
:title (tr "workspace.layout-grid.editor.padding.horizontal")}
:title (tr "workspace.layout_grid.editor.padding.horizontal")}
[:span {:class (stl/css :icon)}
deprecated-icon/padding-left-right]
[:> deprecated-input/numeric-input*
{:className (stl/css :numeric-input)
:placeholder (tr "settings.multiple")
:aria-label (tr "workspace.layout-grid.editor.padding.horizontal")
:aria-label (tr "workspace.layout_grid.editor.padding.horizontal")
:on-change on-p2-change
:on-focus on-focus-p2
:on-blur on-padding-blur
@@ -538,18 +547,18 @@
:icon i/padding-top
:min 0
:name :p1
:property (tr "workspace.layout-grid.editor.padding.top")
:property (tr "workspace.layout_grid.editor.padding.top")
:applied-tokens applied-tokens
:values value}]
[:div {:class (stl/css :padding-multiple)
:title (tr "workspace.layout-grid.editor.padding.top")}
:title (tr "workspace.layout_grid.editor.padding.top")}
[:span {:class (stl/css :icon)}
deprecated-icon/padding-top]
[:> deprecated-input/numeric-input*
{:class (stl/css :numeric-input)
:placeholder "--"
:aria-label (tr "workspace.layout-grid.editor.padding.top")
:aria-label (tr "workspace.layout_grid.editor.padding.top")
:data-attr "p1"
:on-change on-p1-change
:on-focus on-focus-p1
@@ -567,18 +576,18 @@
:min 0
:name :p2
:align :right
:property (tr "workspace.layout-grid.editor.padding.right")
:property (tr "workspace.layout_grid.editor.padding.right")
:applied-tokens applied-tokens
:values value}]
[:div {:class (stl/css :padding-multiple)
:title (tr "workspace.layout-grid.editor.padding.right")}
:title (tr "workspace.layout_grid.editor.padding.right")}
[:span {:class (stl/css :icon)}
deprecated-icon/padding-right]
[:> deprecated-input/numeric-input*
{:class (stl/css :numeric-input)
:placeholder "--"
:aria-label (tr "workspace.layout-grid.editor.padding.right")
:aria-label (tr "workspace.layout_grid.editor.padding.right")
:data-attr "p2"
:on-change on-p2-change
:on-focus on-focus-p2
@@ -595,18 +604,18 @@
:icon i/padding-bottom
:min 0
:name :p3
:property (tr "workspace.layout-grid.editor.padding.bottom")
:property (tr "workspace.layout_grid.editor.padding.bottom")
:applied-tokens applied-tokens
:values value}]
[:div {:class (stl/css :padding-multiple)
:title (tr "workspace.layout-grid.editor.padding.bottom")}
:title (tr "workspace.layout_grid.editor.padding.bottom")}
[:span {:class (stl/css :icon)}
deprecated-icon/padding-bottom]
[:> deprecated-input/numeric-input*
{:class (stl/css :numeric-input)
:placeholder "--"
:aria-label (tr "workspace.layout-grid.editor.padding.bottom")
:aria-label (tr "workspace.layout_grid.editor.padding.bottom")
:data-attr "p3"
:on-change on-p3-change
:on-focus on-focus-p3
@@ -624,18 +633,18 @@
:min 0
:align :right
:name :p4
:property (tr "workspace.layout-grid.editor.padding.left")
:property (tr "workspace.layout_grid.editor.padding.left")
:applied-tokens applied-tokens
:values value}]
[:div {:class (stl/css :padding-multiple)
:title (tr "workspace.layout-grid.editor.padding.left")}
:title (tr "workspace.layout_grid.editor.padding.left")}
[:span {:class (stl/css :icon)}
deprecated-icon/padding-left]
[:> deprecated-input/numeric-input*
{:class (stl/css :numeric-input)
:placeholder "--"
:aria-label (tr "workspace.layout-grid.editor.padding.left")
:aria-label (tr "workspace.layout_grid.editor.padding.left")
:data-attr "p4"
:on-change on-p4-change
:on-focus on-focus-p4
@@ -670,12 +679,14 @@
(= type :multiple)
[:> multiple-padding-selection* props])]
[:> icon-button* {:variant "ghost"
:aria-label (tr "workspace.layout-grid.editor.padding.expand")
:aria-pressed (= type :multiple)
:data-type (d/name type)
:on-click on-type-change'
:icon i/padding-extended}]]))
[:button {:class (stl/css-case
:padding-toggle true
:selected (= type :multiple))
:title (tr "workspace.layout_grid.editor.padding.expand")
:aria-label (tr "workspace.layout_grid.editor.padding.expand")
:data-type (d/name type)
:on-click on-type-change'}
deprecated-icon/padding-extended]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GAP
@@ -820,22 +831,25 @@
;; GRID COMPONENTS
(mf/defc direction-row-grid*
(mf/defc direction-row-grid
{::mf/props :obj}
[{:keys [value on-change] :as props}]
[:> radio-buttons* {:class (stl/css :direction-row-grid)
:selected (d/name value)
:on-change on-change
:name "grid-direction"
:options [{:id "grid-direction-row"
:icon (dir-icons-refactor :row)
:label "Row"
:value "row"}
{:id "grid-direction-column"
:icon (dir-icons-refactor :column)
:label "Column"
:value "column"}]}])
[:& radio-buttons {:class (stl/css :direction-row-grid)
:selected (d/name value)
:decode-fn keyword
:on-change on-change
:name "grid-direction"}
[:& radio-button {:value "row"
:id "grid-direction-row"
:title "Row"
:icon (dir-icons-refactor :row)}]
[:& radio-button {:value "column"
:id "grid-direction-column"
:title "Column"
:icon (dir-icons-refactor :column)}]])
(mf/defc grid-edit-mode*
(mf/defc grid-edit-mode
{::mf/props :obj}
[{:keys [id]}]
(let [edition (mf/deref refs/selected-edition)
active? (= id edition)
@@ -847,65 +861,81 @@
(if-not active?
(st/emit! (udw/start-edition-mode id))
(st/emit! :interrupt))))]
[:button
{:class (stl/css :edit-mode-btn)
:alt "Grid edit mode"
:on-click toggle-edit-mode}
(tr "workspace.layout_grid.editor.options.edit-grid")]))
[:> button* {:variant "secondary"
:class (stl/css :edit-mode-btn)
:on-click toggle-edit-mode}
(tr "workspace.layout-grid.editor.options.edit-grid")]))
(mf/defc align-grid-row*
{::mf/private true}
(mf/defc align-grid-row
{::mf/props :obj
::mf/private true}
[{:keys [is-column value on-change]}]
(let [type (if ^boolean is-column "column" "row")]
[:> radio-buttons* {:class (stl/css :align-grid-row)
:selected (d/name value)
:on-change on-change
:name (dm/str "flex-align-items-" type)
:options [{:id (dm/str "align-items-start-" type)
:icon (get-layout-flex-icon :align-items :start is-column)
:label "Align items start"
:value "start"}
{:id (dm/str "align-items-center-" type)
:icon (get-layout-flex-icon :align-items :center is-column)
:label "Align items center"
:value "center"}
{:id (dm/str "align-items-end-" type)
:icon (get-layout-flex-icon :align-items :end is-column)
:label "Align items end"
:value "end"}]}]))
[:& radio-buttons {:class (stl/css :align-grid-row)
:selected (d/name value)
:decode-fn keyword
:on-change on-change
:name (dm/str "flex-align-items-" type)}
[:& radio-button {:value "start"
:icon (get-layout-grid-icon :align-items :start is-column)
:title "Align items start"
:id (dm/str "align-items-start-" type)}]
[:& radio-button {:value "center"
:icon (get-layout-grid-icon :align-items :center is-column)
:title "Align items center"
:id (dm/str "align-items-center-" type)}]
[:& radio-button {:value "end"
:icon (get-layout-grid-icon :align-items :end is-column)
:title "Align items end"
:id (dm/str "align-items-end-" type)}]]))
(mf/defc justify-grid-row*
{::mf/private true}
(mf/defc justify-grid-row
{::mf/props :obj
::mf/private :obj}
[{:keys [is-column value on-change]}]
(let [type (if ^boolean is-column "column" "row")]
[:> radio-buttons* {:class (stl/css :justify-grid-row)
:selected (d/name value)
:on-change on-change
:name (dm/str "grid-justify-items-" type)
:options [{:id (dm/str "justify-items-start-" type)
:icon (get-layout-grid-icon :justify-items :start is-column)
:label "Justify items start"
:value "start"}
{:id (dm/str "justify-items-center-" type)
:icon (get-layout-grid-icon :justify-items :center is-column)
:label "Justify items center"
:value "center"}
{:id (dm/str "justify-items-end-" type)
:icon (get-layout-grid-icon :justify-items :end is-column)
:label "Justify items end"
:value "end"}
{:id (dm/str "justify-items-space-around-" type)
:icon (get-layout-grid-icon :justify-items :space-around is-column)
:label "Justify items space-around"
:value "space-around"}
{:id (dm/str "justify-items-space-between-" type)
:icon (get-layout-grid-icon :justify-items :space-between is-column)
:label "Justify items space-between"
:value "space-between"}
{:id (dm/str "justify-items-stretch-" type)
:icon (get-layout-grid-icon :justify-items :stretch is-column)
:label "Justify items stretch"
:value "stretch"}]}]))
[:& radio-buttons {:class (stl/css :justify-grid-row)
:selected (d/name value)
:on-change on-change
:decode-fn keyword
:name (dm/str "grid-justify-items-" type)}
[:& radio-button {:key "justify-item-start"
:value "start"
:icon (get-layout-grid-icon :justify-items :start is-column)
:title "Justify items start"
:id (dm/str "justify-items-start-" type)}]
[:& radio-button {:key "justify-item-center"
:value "center"
:icon (get-layout-grid-icon :justify-items :center is-column)
:title "Justify items center"
:id (dm/str "justify-items-center-" type)}]
[:& radio-button {:key "justify-item-end"
:value "end"
:icon (get-layout-grid-icon :justify-items :end is-column)
:title "Justify items end"
:id (dm/str "justify-items-end-" type)}]
[:& radio-button {:key "justify-item-space-around"
:value "space-around"
:icon (get-layout-grid-icon :justify-items :space-around is-column)
:title "Justify items space-around"
:id (dm/str "justify-items-space-around-" type)}]
[:& radio-button {:key "justify-item-space-between"
:value "space-between"
:icon (get-layout-grid-icon :justify-items :space-between is-column)
:title "Justify items space-between"
:id (dm/str "justify-items-space-between-" type)}]
[:& radio-button {:key "justify-item-stretch"
:value "stretch"
:icon (get-layout-grid-icon :justify-items :stretch is-column)
:title "Justify items stretch"
:id (dm/str "justify-items-stretch-" type)}]]))
(defn- manage-values
[{:keys [type value]}]
@@ -916,7 +946,8 @@
:fixed (fmt/format-pixels value)
value))
(mf/defc grid-track-info*
(mf/defc grid-track-info
{::mf/props :obj}
[{:keys [is-column
type
index
@@ -999,7 +1030,8 @@
:data-index index
:icon i/remove}]]))
(mf/defc grid-columns-row*
(mf/defc grid-columns-row
{::mf/props :obj}
[{:keys [is-column expanded? column-values toggle add-new-element set-column-value set-column-type
remove-element reorder-track hover-track on-select-track]}]
(let [column-num (count column-values)
@@ -1020,38 +1052,27 @@
[:div {:class (stl/css :grid-tracks) :data-testid testid}
[:div {:class (stl/css :grid-track-header)}
[:> icon-button* {:variant "secondary"
:class (stl/css :expand-icon)
:aria-pressed expanded?
:aria-label (tr "labels.options")
:on-click toggle
:icon i/menu}]
#_[:button {:class (stl/css :expand-icon) :on-click toggle} deprecated-icon/menu]
[:button {:class (stl/css :expand-icon) :on-click toggle} deprecated-icon/menu]
[:div {:class (stl/css :track-title) :on-click toggle}
[:div {:class (stl/css :track-name) :title track-name} track-name]
[:div {:class (stl/css :track-detail) :title track-detail} track-detail]]
[:> icon-button* {:variant "secondary"
:class (stl/css :add-column)
:aria-label (tr "labels.add")
:on-click add-track
:icon i/add}]
#_[:button {:class (stl/css :add-column) :on-click add-track} deprecated-icon/add]]
[:button {:class (stl/css :add-column) :on-click add-track} deprecated-icon/add]]
(when expanded?
[:> h/sortable-container* {}
[:div {:class (stl/css :grid-tracks-info-container)}
(for [[index column] (d/enumerate column-values)]
[:> grid-track-info* {:key (dm/str index "-" (d/name type))
:type type
:is-column is-column
:index index
:column column
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element
:reorder-track reorder-track
:hover-track hover-track
:on-select-track on-select-track}])]])]))
[:& grid-track-info {:key (dm/str index "-" (d/name type))
:type type
:is-column is-column
:index index
:column column
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element
:reorder-track reorder-track
:hover-track hover-track
:on-select-track on-select-track}])]])]))
;; LAYOUT COMPONENT
@@ -1187,8 +1208,8 @@
(mf/deps layout-type ids)
(fn [dir]
(if (= :flex layout-type)
(st/emit! (dwsl/update-layout ids {:layout-flex-dir (keyword dir)}))
(st/emit! (dwsl/update-layout ids {:layout-grid-dir (keyword dir)})))))
(st/emit! (dwsl/update-layout ids {:layout-flex-dir dir}))
(st/emit! (dwsl/update-layout ids {:layout-grid-dir dir})))))
;; Align grid
align-items-row (:layout-align-items values)
@@ -1198,13 +1219,13 @@
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-justify-items (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-justify-items value}))))
on-row-align-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-align-items (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-align-items value}))))
;; Justify grid
grid-justify-content-row (:layout-justify-content values)
@@ -1214,13 +1235,13 @@
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-align-content (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-align-content value}))))
on-row-justify-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-justify-content (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-justify-content value}))))
on-toggle-dropdown-visibility
(mf/use-fn #(swap! show-dropdown* not))
@@ -1293,31 +1314,31 @@
:flex
[:div {:class (stl/css :flex-layout-menu)}
[:div {:class (stl/css :first-row)}
[:> align-row* {:is-column is-column
:value align-items
:on-change set-align-items}]
[:& align-row {:is-column is-column
:value align-items
:on-change set-align-items}]
[:> direction-row-flex* {:on-change on-direction-change
:value saved-dir}]
[:& direction-row-flex {:on-change on-direction-change
:value saved-dir}]
[:> wrap-row* {:wrap-type wrap-type
:on-click toggle-wrap}]]
[:& wrap-row {:wrap-type wrap-type
:on-click toggle-wrap}]]
[:div {:class (stl/css :middle-row)}
[:div {:class (stl/css :help-button-wrapper)}
[:> justify-content-row* {:is-column is-column
:justify-content justify-content
:on-change set-justify-content}]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.help-center")
:on-click open-flex-help
:icon i/help}]]
(when (= :wrap wrap-type)
[:> align-content-row* {:is-column is-column
:value align-content
:on-change on-align-content-change}])]
[:div {:class (stl/css :second-row :help-button-wrapper)}
[:& justify-content-row {:is-column is-column
:justify-content justify-content
:on-change set-justify-content}]
[:div {:class (stl/css :last-row)}
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.help-center")
:on-click open-flex-help
:icon i/help}]]
(when (= :wrap wrap-type)
[:div {:class (stl/css :third-row)}
[:& align-content-row {:is-column is-column
:value align-content
:on-change on-align-content-change}]])
[:div {:class (stl/css :forth-row)}
[:> gap-section* {:is-column is-column
:wrap-type wrap-type
:on-change on-gap-change
@@ -1335,7 +1356,7 @@
[:div {:class (stl/css :grid-layout-menu)}
(when (= 1 (count ids))
[:div {:class (stl/css :edit-grid-wrapper)}
[:> grid-edit-mode* {:id (first ids)}]
[:& grid-edit-mode {:id (first ids)}]
[:> icon-button* {:variant "ghost"
:aria-label (tr "labels.help-center")
:on-click open-grid-help
@@ -1344,23 +1365,23 @@
[:div {:class (stl/css :first-row)}
[:div {:class (stl/css :direction-edit)}
[:div {:class (stl/css :direction)}
[:> direction-row-grid* {:value saved-grid-dir
:on-change on-direction-change}]]]
[:& direction-row-grid {:value saved-grid-dir
:on-change on-direction-change}]]]
[:> align-grid-row* {:is-column false
:value align-items-row
:on-change on-row-align-change}]
[:> align-grid-row* {:is-column true
:value align-items-column
:on-change on-column-align-change}]]
[:& align-grid-row {:is-column false
:value align-items-row
:on-change on-row-align-change}]
[:& align-grid-row {:is-column true
:value align-items-column
:on-change on-column-align-change}]]
[:div {:class (stl/css :row :grid-layout-align)}
[:> justify-grid-row* {:is-column true
:value grid-justify-content-column
:on-change on-column-justify-change}]
[:> justify-grid-row* {:is-column false
:value grid-justify-content-row
:on-change on-row-justify-change}]]
[:& justify-grid-row {:is-column true
:value grid-justify-content-column
:on-change on-column-justify-change}]
[:& justify-grid-row {:is-column false
:value grid-justify-content-row
:on-change on-row-justify-change}]]
[:div {:class (stl/css :gap-row)}
[:> gap-section* {:on-change on-gap-change
@@ -1386,7 +1407,7 @@
(mf/use-fn
(mf/deps ids)
(fn [dir]
(st/emit! (dwsl/update-layout ids {:layout-grid-dir (keyword dir)}))))
(st/emit! (dwsl/update-layout ids {:layout-grid-dir dir}))))
on-gap-change
(mf/use-fn
@@ -1425,13 +1446,13 @@
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-justify-items (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-justify-items value}))))
on-row-align-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-align-items (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-align-items value}))))
;; Justify grid
grid-justify-content-row (:layout-justify-content values)
@@ -1441,13 +1462,13 @@
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-align-content (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-align-content value}))))
on-row-justify-change
(mf/use-fn
(mf/deps ids)
(fn [value]
(st/emit! (dwsl/update-layout ids {:layout-justify-content (keyword value)}))))
(st/emit! (dwsl/update-layout ids {:layout-justify-content value}))))
columns-open? (mf/use-state false)
rows-open? (mf/use-state false)
@@ -1528,36 +1549,35 @@
:aria-label (tr "labels.help-center")
:on-click open-grid-help
:icon i/help}]
[:> button* {:variant "secondary"
:class (stl/css :exit-btn)
:on-click #(st/emit! (udw/clear-edition-mode))}
(tr "workspace.layout-grid.editor.options.exit")]]
[:button {:class (stl/css :exit-btn)
:on-click #(st/emit! (udw/clear-edition-mode))}
(tr "workspace.layout_grid.editor.options.exit")]]
[:div {:class (stl/css :row :first-row)}
[:div {:class (stl/css :direction-edit)}
[:div {:class (stl/css :direction)}
[:> direction-row-grid* {:value saved-grid-dir
:on-change on-direction-change}]]]
[:& direction-row-grid {:value saved-grid-dir
:on-change on-direction-change}]]]
[:> align-grid-row* {:is-column false
:value align-items-row
:on-change on-row-align-change}]
[:& align-grid-row {:is-column false
:value align-items-row
:on-change on-row-align-change}]
[:> align-grid-row* {:is-column true
:value align-items-column
:on-change on-column-align-change}]]
[:& align-grid-row {:is-column true
:value align-items-column
:on-change on-column-align-change}]]
[:div {:class (stl/css :row :grid-layout-align)}
[:> justify-grid-row* {:is-column true
:value grid-justify-content-column
:on-change on-column-justify-change}]
[:> justify-grid-row* {:is-column false
:value grid-justify-content-row
:on-change on-row-justify-change}]
[:& justify-grid-row {:is-column true
:value grid-justify-content-column
:on-change on-column-justify-change}]
[:& justify-grid-row {:is-column false
:value grid-justify-content-row
:on-change on-row-justify-change}]
[:> icon-button* {:variant "ghost"
:class (stl/css :locate-button)
:aria-label (tr "workspace.layout-grid.editor.top-bar.locate.tooltip")
:aria-label (tr "workspace.layout_grid.editor.top-bar.locate.tooltip")
:on-click handle-locate-grid
:icon i/locate}]]
@@ -1567,33 +1587,33 @@
:applied-tokens applied-tokens
:value (:layout-gap values)}]]
[:div {:class (stl/css :padding-row)}
[:div {:class (stl/css :padding-row :padding-section)}
[:> padding-section* {:value (:layout-padding values)
:type (:layout-padding-type values)
:on-type-change on-padding-type-change
:on-change on-padding-change}]]
[:div {:class (stl/css :grid-tracks-row)}
[:> grid-columns-row* {:is-column true
:expanded? @columns-open?
:toggle toggle-columns-open
:column-values column-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element
:reorder-track reorder-track
:hover-track hover-track
:on-select-track handle-select-track}]
[:& grid-columns-row {:is-column true
:expanded? @columns-open?
:toggle toggle-columns-open
:column-values column-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element
:reorder-track reorder-track
:hover-track hover-track
:on-select-track handle-select-track}]
[:> grid-columns-row* {:is-column false
:expanded? @rows-open?
:toggle toggle-rows-open
:column-values rows-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element
:reorder-track reorder-track
:hover-track hover-track
:on-select-track handle-select-track}]]]))
[:& grid-columns-row {:is-column false
:expanded? @rows-open?
:toggle toggle-rows-open
:column-values rows-values
:add-new-element add-new-element
:set-column-value set-column-value
:set-column-type set-column-type
:remove-element remove-element
:reorder-track reorder-track
:hover-track hover-track
:on-select-track handle-select-track}]]]))

Some files were not shown because too many files have changed in this diff Show More