mirror of
https://github.com/penpot/penpot.git
synced 2025-12-31 02:18:40 -05:00
Compare commits
19 Commits
eva-replac
...
niwinz-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f65259b1 | ||
|
|
9280d3937c | ||
|
|
8b12e5d996 | ||
|
|
63763aace7 | ||
|
|
45f98b716e | ||
|
|
fd2e5e31e2 | ||
|
|
d1f660c70c | ||
|
|
119d901ce3 | ||
|
|
856dd2238d | ||
|
|
31017654bb | ||
|
|
91337c074a | ||
|
|
0200ba15f2 | ||
|
|
957d51c68f | ||
|
|
a0fd066af1 | ||
|
|
4ed307be7a | ||
|
|
c4260c17b2 | ||
|
|
692471c927 | ||
|
|
5b62936ab4 | ||
|
|
ada0d8b293 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -80,4 +80,4 @@ node_modules
|
||||
/playwright/.cache/
|
||||
/render-wasm/target/
|
||||
/**/.yarn/*
|
||||
/.pnpm-store
|
||||
/.pnpm-store
|
||||
@@ -12,9 +12,6 @@
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
|
||||
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
||||
|
||||
|
||||
## 2.12.0 (Unreleased)
|
||||
|
||||
@@ -103,7 +100,6 @@ example. It's still usable as before, we just removed the example.
|
||||
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
|
||||
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
||||
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
||||
- Fix problem with plugins content attribute [Plugins #209](https://github.com/penpot/penpot-plugins/issues/209)
|
||||
|
||||
## 2.11.1
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
integrant/integrant {:mvn/version "1.0.0"}
|
||||
|
||||
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||
funcool/cuerdas {:mvn/version "2025.06.16-414"}
|
||||
funcool/promesa
|
||||
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
||||
|
||||
@@ -2813,15 +2813,13 @@
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
||||
|
||||
;; If there is an alt-duplication we change to root
|
||||
;; For variants so the copy is made as a child of root
|
||||
;; If there is an alt-duplication of a variant, change its parent to root
|
||||
;; so the copy is made as a child of root
|
||||
;; This is because inside a variant-container can't be a copy
|
||||
;; For other shape this way the layout won't be changed when duplicated
|
||||
;; and if you move outside the layout will not change
|
||||
shapes (map (fn [shape]
|
||||
(cond-> shape
|
||||
alt-duplication?
|
||||
(assoc :parent-id uuid/zero :frame-id uuid/zero)))
|
||||
(if (and alt-duplication? (ctk/is-variant? shape))
|
||||
(assoc shape :parent-id uuid/zero :frame-id nil)
|
||||
shape))
|
||||
shapes)
|
||||
|
||||
|
||||
|
||||
@@ -267,4 +267,3 @@
|
||||
(-> (stp/convert-to-path shape objects)
|
||||
(update :content impl/path-data))))
|
||||
|
||||
(dm/export impl/decode-segments)
|
||||
|
||||
@@ -565,9 +565,6 @@
|
||||
(def check-content
|
||||
(sm/check-fn schema:content))
|
||||
|
||||
(def decode-segments
|
||||
(sm/lazy-decoder schema:segments sm/json-transformer))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; CONSTRUCTORS & PREDICATES
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -1575,10 +1575,10 @@ Will return a value that matches this schema:
|
||||
(if (map? shadow)
|
||||
(let [legacy-shadow-type (get "type" shadow)]
|
||||
(-> shadow
|
||||
(set/rename-keys {"x" :offset-x
|
||||
"offsetX" :offset-x
|
||||
"y" :offset-y
|
||||
"offsetY" :offset-y
|
||||
(set/rename-keys {"x" :offsetX
|
||||
"offsetX" :offsetX
|
||||
"y" :offsetY
|
||||
"offsetY" :offsetY
|
||||
"blur" :blur
|
||||
"spread" :spread
|
||||
"color" :color
|
||||
@@ -1589,7 +1589,7 @@ Will return a value that matches this schema:
|
||||
(= "false" %) false
|
||||
(= legacy-shadow-type "innerShadow") true
|
||||
:else false))
|
||||
(select-keys [:offset-x :offset-y :blur :spread :color :inset])))
|
||||
(select-keys [:offsetX :offsetY :blur :spread :color :inset])))
|
||||
shadow))]
|
||||
(cond
|
||||
;; Reference value - keep as string
|
||||
@@ -1860,8 +1860,8 @@ Will return a value that matches this schema:
|
||||
(mapv (fn [shadow]
|
||||
(if (map? shadow)
|
||||
(-> shadow
|
||||
(set/rename-keys {:offset-x "offsetX"
|
||||
:offset-y "offsetY"
|
||||
(set/rename-keys {:offsetX "offsetX"
|
||||
:offsetY "offsetY"
|
||||
:blur "blur"
|
||||
:spread "spread"
|
||||
:color "color"
|
||||
|
||||
@@ -1897,15 +1897,15 @@
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-single")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
|
||||
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "multiple shadow token"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-multiple")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offset-x "0", :offset-y "2px", :blur "4px", :spread "0", :color "#000", :inset true}
|
||||
{:offset-x "0", :offset-y "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
|
||||
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset true}
|
||||
{:offsetX "0", :offsetY "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "shadow token with reference"
|
||||
@@ -1918,7 +1918,7 @@
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-type")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offset-x "0", :offset-y "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
|
||||
(t/is (= [{:offsetX "0", :offsetY "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "shadow token with description"
|
||||
@@ -1937,14 +1937,14 @@
|
||||
(ctob/make-token
|
||||
{:name "shadow.single"
|
||||
:type :shadow
|
||||
:value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}]
|
||||
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}]
|
||||
:description "A single shadow"})
|
||||
"shadow.multiple"
|
||||
(ctob/make-token
|
||||
{:name "shadow.multiple"
|
||||
:type :shadow
|
||||
:value [{:offset-x "0" :offset-y "2px" :blur "4px" :spread "0" :color "#0000001A"}
|
||||
{:offset-x "0" :offset-y "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
|
||||
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}
|
||||
{:offsetX "0" :offsetY "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
|
||||
"shadow.ref"
|
||||
(ctob/make-token
|
||||
{:name "shadow.ref"
|
||||
@@ -1991,7 +1991,7 @@
|
||||
(ctob/make-token
|
||||
{:name "shadow.test"
|
||||
:type :shadow
|
||||
:value [{:offset-x "1" :offset-y "1" :blur "1" :spread "1" :color "red" :inset true}]
|
||||
:value [{:offsetX "1" :offsetY "1" :blur "1" :spread "1" :color "red" :inset true}]
|
||||
:description "Round trip test"})
|
||||
"shadow.ref"
|
||||
(ctob/make-token
|
||||
|
||||
@@ -59,38 +59,6 @@ RUN set -eux; \
|
||||
corepack enable; \
|
||||
rm -rf /tmp/nodejs.tar.gz;
|
||||
|
||||
|
||||
################################################################################
|
||||
## CADDYSERVER SETUP
|
||||
################################################################################
|
||||
|
||||
FROM base AS setup-caddy
|
||||
|
||||
ENV CADDY_VERSION=2.10.2
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
BINARY_URL="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_arm64.tar.gz"; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
BINARY_URL="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_amd64.tar.gz"; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac; \
|
||||
curl -LfsSo /tmp/caddy.tar.gz ${BINARY_URL}; \
|
||||
mkdir -p /tmp/caddy; \
|
||||
cd /tmp/caddy; \
|
||||
tar -xf /tmp/caddy.tar.gz; \
|
||||
chown -R root /tmp/caddy; \
|
||||
mv /tmp/caddy/caddy /usr/bin/; \
|
||||
rm -rf /tmp/caddy.tar.gz; \
|
||||
rm -rf /tmp/caddy;
|
||||
|
||||
################################################################################
|
||||
## JVM SETUP
|
||||
################################################################################
|
||||
@@ -383,7 +351,6 @@ COPY --from=setup-utils /opt/utils /opt/utils
|
||||
COPY --from=setup-rust /opt/cargo /opt/cargo
|
||||
COPY --from=setup-rust /opt/rustup /opt/rustup
|
||||
COPY --from=setup-rust /opt/emsdk /opt/emsdk
|
||||
COPY --from=setup-caddy /usr/bin/caddy /usr/bin/caddy
|
||||
|
||||
COPY files/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY files/nginx-mime.types /etc/nginx/mime.types
|
||||
@@ -394,7 +361,6 @@ COPY files/vimrc /root/.vimrc
|
||||
COPY files/tmux.conf /root/.tmux.conf
|
||||
COPY files/sudoers /etc/sudoers
|
||||
|
||||
COPY files/Caddyfile /home/
|
||||
COPY files/start-tmux.sh /home/start-tmux.sh
|
||||
COPY files/start-tmux-back.sh /home/start-tmux-back.sh
|
||||
COPY files/entrypoint.sh /home/entrypoint.sh
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
localhost:3449 {
|
||||
tls internal
|
||||
reverse_proxy localhost:4449
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
nginx;
|
||||
caddy run -c /home/Caddyfile;
|
||||
nginx
|
||||
tail -f /dev/null
|
||||
|
||||
@@ -12,7 +12,7 @@ http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 100;
|
||||
keepalive_timeout 0;
|
||||
types_hash_max_size 2048;
|
||||
server_tokens off;
|
||||
|
||||
@@ -55,7 +55,7 @@ http {
|
||||
proxy_cache_key "$host$request_uri";
|
||||
|
||||
server {
|
||||
listen 4449 default_server;
|
||||
listen 3449 default_server;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 300M;
|
||||
@@ -231,6 +231,7 @@ http {
|
||||
}
|
||||
|
||||
add_header Cache-Control "no-store";
|
||||
add_header Connection close always;
|
||||
# This header is what we need to use on prod
|
||||
# add_header Cache-Control "public, must-revalidate, max-age=0";
|
||||
try_files $uri /index.html$is_args$args /index.html =404;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
||||
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
@@ -16,9 +16,9 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"generic-pool": "^3.9.0",
|
||||
"inflation": "^2.1.0",
|
||||
"ioredis": "^5.8.1",
|
||||
"playwright": "^1.55.1",
|
||||
"raw-body": "^3.0.1",
|
||||
"ioredis": "^5.8.2",
|
||||
"playwright": "^1.57.0",
|
||||
"raw-body": "^3.0.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"svgo": "penpot/svgo#v3.1",
|
||||
"xml-js": "^1.6.11",
|
||||
|
||||
@@ -18,15 +18,4 @@ cp ../.yarnrc.yml target/;
|
||||
cp yarn.lock target/;
|
||||
cp package.json target/;
|
||||
|
||||
cat <<EOF | tee target/setup
|
||||
#/usr/bin/env bash
|
||||
set -e;
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install
|
||||
yarn run playwright install chromium;
|
||||
EOF
|
||||
|
||||
chmod +x target/setup;
|
||||
|
||||
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/app.js;
|
||||
|
||||
@@ -243,7 +243,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bytes@npm:3.1.2":
|
||||
"bytes@npm:~3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "bytes@npm:3.1.2"
|
||||
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
|
||||
@@ -442,7 +442,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"depd@npm:2.0.0, depd@npm:~2.0.0":
|
||||
"depd@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "depd@npm:2.0.0"
|
||||
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
|
||||
@@ -577,9 +577,9 @@ __metadata:
|
||||
date-fns: "npm:^4.1.0"
|
||||
generic-pool: "npm:^3.9.0"
|
||||
inflation: "npm:^2.1.0"
|
||||
ioredis: "npm:^5.8.1"
|
||||
playwright: "npm:^1.55.1"
|
||||
raw-body: "npm:^3.0.1"
|
||||
ioredis: "npm:^5.8.2"
|
||||
playwright: "npm:^1.57.0"
|
||||
raw-body: "npm:^3.0.2"
|
||||
source-map-support: "npm:^0.5.21"
|
||||
svgo: "penpot/svgo#v3.1"
|
||||
ws: "npm:^8.18.3"
|
||||
@@ -682,16 +682,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-errors@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "http-errors@npm:2.0.0"
|
||||
"http-errors@npm:~2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "http-errors@npm:2.0.1"
|
||||
dependencies:
|
||||
depd: "npm:2.0.0"
|
||||
inherits: "npm:2.0.4"
|
||||
setprototypeof: "npm:1.2.0"
|
||||
statuses: "npm:2.0.1"
|
||||
toidentifier: "npm:1.0.1"
|
||||
checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19
|
||||
depd: "npm:~2.0.0"
|
||||
inherits: "npm:~2.0.4"
|
||||
setprototypeof: "npm:~1.2.0"
|
||||
statuses: "npm:~2.0.2"
|
||||
toidentifier: "npm:~1.0.1"
|
||||
checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -715,15 +715,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:0.7.0":
|
||||
version: 0.7.0
|
||||
resolution: "iconv-lite@npm:0.7.0"
|
||||
dependencies:
|
||||
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
|
||||
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:^0.6.2":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
@@ -733,6 +724,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:~0.7.0":
|
||||
version: 0.7.0
|
||||
resolution: "iconv-lite@npm:0.7.0"
|
||||
dependencies:
|
||||
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
|
||||
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ieee754@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "ieee754@npm:1.2.1"
|
||||
@@ -754,16 +754,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"inherits@npm:2.0.4, inherits@npm:~2.0.3":
|
||||
"inherits@npm:~2.0.3, inherits@npm:~2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "inherits@npm:2.0.4"
|
||||
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ioredis@npm:^5.8.1":
|
||||
version: 5.8.1
|
||||
resolution: "ioredis@npm:5.8.1"
|
||||
"ioredis@npm:^5.8.2":
|
||||
version: 5.8.2
|
||||
resolution: "ioredis@npm:5.8.2"
|
||||
dependencies:
|
||||
"@ioredis/commands": "npm:1.4.0"
|
||||
cluster-key-slot: "npm:^1.1.0"
|
||||
@@ -774,7 +774,7 @@ __metadata:
|
||||
redis-errors: "npm:^1.2.0"
|
||||
redis-parser: "npm:^3.0.0"
|
||||
standard-as-callback: "npm:^2.1.0"
|
||||
checksum: 10c0/4ed66444017150da027bce940a24bf726994691e2a7b3aa11d52f8aeb37f258068cc171af4d9c61247acafc28eb086fa8a7c79420b8e8d2907d2f74f39584465
|
||||
checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1105,27 +1105,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright-core@npm:1.55.1":
|
||||
version: 1.55.1
|
||||
resolution: "playwright-core@npm:1.55.1"
|
||||
"playwright-core@npm:1.57.0":
|
||||
version: 1.57.0
|
||||
resolution: "playwright-core@npm:1.57.0"
|
||||
bin:
|
||||
playwright-core: cli.js
|
||||
checksum: 10c0/39837a8c1232ec27486eac8c3fcacc0b090acc64310f7f9004b06715370fc426f944e3610fe8c29f17cd3d68280ed72c75f660c02aa5b5cf0eb34bab0031308f
|
||||
checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.55.1":
|
||||
version: 1.55.1
|
||||
resolution: "playwright@npm:1.55.1"
|
||||
"playwright@npm:^1.57.0":
|
||||
version: 1.57.0
|
||||
resolution: "playwright@npm:1.57.0"
|
||||
dependencies:
|
||||
fsevents: "npm:2.3.2"
|
||||
playwright-core: "npm:1.55.1"
|
||||
playwright-core: "npm:1.57.0"
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
bin:
|
||||
playwright: cli.js
|
||||
checksum: 10c0/b84a97b0d764403df512f5bbb10c7343974e151a28202cc06f90883a13e8a45f4491a0597f0ae5fb03a026746cbc0d200f0f32195bfaa381aee5ca5770626771
|
||||
checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1160,15 +1160,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"raw-body@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "raw-body@npm:3.0.1"
|
||||
"raw-body@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "raw-body@npm:3.0.2"
|
||||
dependencies:
|
||||
bytes: "npm:3.1.2"
|
||||
http-errors: "npm:2.0.0"
|
||||
iconv-lite: "npm:0.7.0"
|
||||
unpipe: "npm:1.0.0"
|
||||
checksum: 10c0/892f4fbd21ecab7e2fed0f045f7af9e16df7e8050879639d4e482784a2f4640aaaa33d916a0e98013f23acb82e09c2e3c57f84ab97104449f728d22f65a7d79a
|
||||
bytes: "npm:~3.1.2"
|
||||
http-errors: "npm:~2.0.1"
|
||||
iconv-lite: "npm:~0.7.0"
|
||||
unpipe: "npm:~1.0.0"
|
||||
checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1269,7 +1269,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"setprototypeof@npm:1.2.0":
|
||||
"setprototypeof@npm:~1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "setprototypeof@npm:1.2.0"
|
||||
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
|
||||
@@ -1367,10 +1367,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"statuses@npm:2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "statuses@npm:2.0.1"
|
||||
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
|
||||
"statuses@npm:~2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "statuses@npm:2.0.2"
|
||||
checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1499,7 +1499,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"toidentifier@npm:1.0.1":
|
||||
"toidentifier@npm:~1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "toidentifier@npm:1.0.1"
|
||||
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
|
||||
@@ -1531,7 +1531,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unpipe@npm:1.0.0":
|
||||
"unpipe@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "unpipe@npm:1.0.0"
|
||||
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
diff --git a/lib/zip-fs.js b/lib/zip-fs.js
|
||||
index 1444c0f00e5f1ad6c13521f90a7f3c6659d81116..90e38baef5365c2abbcb9337f7ab37f800e883a4 100644
|
||||
--- a/lib/zip-fs.js
|
||||
+++ b/lib/zip-fs.js
|
||||
@@ -33,12 +33,7 @@ import { initShimAsyncCodec } from "./core/util/stream-codec-shim.js";
|
||||
import { terminateWorkers } from "./core/codec-pool.js";
|
||||
|
||||
let baseURL;
|
||||
-try {
|
||||
- baseURL = import.meta.url;
|
||||
- // eslint-disable-next-line no-unused-vars
|
||||
-} catch (_) {
|
||||
- // ignored
|
||||
-}
|
||||
+
|
||||
configure({ baseURL });
|
||||
configureWebWorker(configure);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
diff --git a/lib/zip-core-base.js b/lib/zip-core-base.js
|
||||
index 142155c8c3ab1a6caa7c370e8a2931a954556ca9..61b0fe6efb91312f437750e0002218f218afad8b 100644
|
||||
--- a/lib/zip-core-base.js
|
||||
+++ b/lib/zip-core-base.js
|
||||
@@ -28,12 +28,6 @@
|
||||
|
||||
import { configure } from "./core/configuration.js";
|
||||
|
||||
-try {
|
||||
- configure({ baseURI: import.meta.url });
|
||||
-} catch {
|
||||
- // ignored
|
||||
-}
|
||||
-
|
||||
export * from "./zip-core-reader.js";
|
||||
export * from "./zip-core-writer.js";
|
||||
export {
|
||||
@@ -8,6 +8,11 @@
|
||||
metosin/reitit-core {:mvn/version "0.9.1"}
|
||||
funcool/okulary {:mvn/version "2022.04.11-16"}
|
||||
|
||||
funcool/tubax
|
||||
{:git/tag "v2025.11.28"
|
||||
:git/sha "2d9a986"
|
||||
:git/url "https://github.com/funcool/tubax.git"}
|
||||
|
||||
funcool/potok2
|
||||
{:git/tag "v2.2"
|
||||
:git/sha "0f7e15a"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"license": "MPL-2.0",
|
||||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
||||
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
@@ -14,10 +14,10 @@
|
||||
"url": "https://github.com/penpot/penpot"
|
||||
},
|
||||
"resolutions": {
|
||||
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"playwright": "1.52.0",
|
||||
"playwright-core": "1.52.0"
|
||||
"playwright-core": "1.52.0",
|
||||
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.8.11#~/.yarn/patches/@zip.js-zip.js-npm-2.8.11-b131c96df8.patch"
|
||||
},
|
||||
"scripts": {
|
||||
"build:app:assets": "node ./scripts/build-app-assets.js",
|
||||
@@ -53,83 +53,74 @@
|
||||
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.52.0",
|
||||
"@storybook/addon-docs": "10.0.4",
|
||||
"@storybook/addon-themes": "10.0.4",
|
||||
"@storybook/addon-vitest": "10.0.4",
|
||||
"@storybook/react-vite": "10.0.4",
|
||||
"@types/node": "^22.15.21",
|
||||
"@penpot/draft-js": "portal:./packages/draft-js",
|
||||
"@penpot/mousetrap": "portal:./packages/mousetrap",
|
||||
"@penpot/plugins-runtime": "1.3.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.1",
|
||||
"@penpot/text-editor": "portal:./text-editor",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@storybook/addon-docs": "10.1.0",
|
||||
"@storybook/addon-themes": "10.1.0",
|
||||
"@storybook/addon-vitest": "10.1.0",
|
||||
"@storybook/react-vite": "10.1.0",
|
||||
"@tokens-studio/sd-transforms": "1.2.11",
|
||||
"@types/node": "^22.19.1",
|
||||
"@vitest/browser": "3.2.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.8.11#~/.yarn/patches/@zip.js-zip.js-npm-2.8.11-b131c96df8.patch",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"compression": "^1.8.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"esbuild": "^0.25.9",
|
||||
"date-fns": "^4.1.0",
|
||||
"esbuild": "^0.25.12",
|
||||
"eventsource-parser": "^3.0.6",
|
||||
"express": "^5.1.0",
|
||||
"fancy-log": "^2.0.0",
|
||||
"getopts": "^2.3.0",
|
||||
"gettext-parser": "^8.0.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-gzip": "^1.4.2",
|
||||
"gulp-mustache": "^5.0.0",
|
||||
"gulp-postcss": "^10.0.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-svg-sprite": "^2.0.3",
|
||||
"jsdom": "^27.0.0",
|
||||
"map-stream": "0.0.7",
|
||||
"marked": "^15.0.12",
|
||||
"mkdirp": "^3.0.1",
|
||||
"mustache": "^4.2.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"p-limit": "^6.2.0",
|
||||
"playwright": "1.56.1",
|
||||
"postcss": "^8.5.4",
|
||||
"postcss-clean": "^1.2.2",
|
||||
"prettier": "3.5.3",
|
||||
"pretty-time": "^1.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.89.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"storybook": "10.0.4",
|
||||
"svg-sprite": "^2.0.4",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^6.3.5",
|
||||
"vitest": "^3.2.0",
|
||||
"wasm-pack": "^0.13.1",
|
||||
"watcher": "^2.3.1",
|
||||
"workerpool": "^9.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@penpot/draft-js": "portal:./vendor/draft-js",
|
||||
"@penpot/hljs": "portal:./vendor/hljs",
|
||||
"@penpot/mousetrap": "portal:./vendor/mousetrap",
|
||||
"@penpot/plugins-runtime": "1.3.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.1",
|
||||
"@penpot/text-editor": "portal:./text-editor",
|
||||
"@tokens-studio/sd-transforms": "1.2.11",
|
||||
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
|
||||
"compression": "^1.8.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"eventsource-parser": "^3.0.6",
|
||||
"highlight.js": "^11.10.0",
|
||||
"js-beautify": "^1.15.4",
|
||||
"jsdom": "^27.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"map-stream": "0.0.7",
|
||||
"marked": "^17.0.1",
|
||||
"mkdirp": "^3.0.1",
|
||||
"mustache": "^4.2.0",
|
||||
"nodemon": "^3.1.11",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"opentype.js": "^1.3.4",
|
||||
"p-limit": "^7.2.0",
|
||||
"playwright": "1.57.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-clean": "^1.2.2",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"prettier": "3.7.1",
|
||||
"pretty-time": "^1.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-virtualized": "^9.22.6",
|
||||
"rimraf": "^6.1.2",
|
||||
"rxjs": "8.0.0-alpha.14",
|
||||
"sax": "^1.4.1",
|
||||
"sass": "^1.89.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"sax": "^1.4.3",
|
||||
"source-map-support": "^0.5.21",
|
||||
"storybook": "10.1.0",
|
||||
"style-dictionary": "5.0.0-rc.1",
|
||||
"svg-sprite": "^2.0.4",
|
||||
"tdigest": "^0.1.2",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"ua-parser-js": "2.0.5",
|
||||
"typescript": "^5.9.3",
|
||||
"ua-parser-js": "2.0.6",
|
||||
"vite": "^6.4.1",
|
||||
"vitest": "^3.2.4",
|
||||
"wasm-pack": "^0.13.1",
|
||||
"watcher": "^2.3.1",
|
||||
"workerpool": "^10.0.1",
|
||||
"xregexp": "^5.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ export const {
|
||||
RichTextEditorUtil,
|
||||
SelectionState,
|
||||
convertFromRaw,
|
||||
convertToRaw
|
||||
convertToRaw,
|
||||
EditorBlock,
|
||||
Editor
|
||||
} = pkg;
|
||||
|
||||
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';
|
||||
@@ -8,7 +8,8 @@
|
||||
"author": "Andrey Antukh",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0",
|
||||
"immutable": "^5.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.17.0",
|
||||
@@ -173,12 +173,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@penpot/draft-js-wrapper@workspace:.":
|
||||
"@penpot/draft-js@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@penpot/draft-js-wrapper@workspace:."
|
||||
resolution: "@penpot/draft-js@workspace:."
|
||||
dependencies:
|
||||
draft-js: "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
|
||||
esbuild: "npm:^0.24.0"
|
||||
immutable: "npm:^5.1.4"
|
||||
peerDependencies:
|
||||
react: ">=0.17.0"
|
||||
react-dom: ">=0.17.0"
|
||||
@@ -320,6 +321,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immutable@npm:^5.1.4":
|
||||
version: 5.1.4
|
||||
resolution: "immutable@npm:5.1.4"
|
||||
checksum: 10c0/f1c98382e4cde14a0b218be3b9b2f8441888da8df3b8c064aa756071da55fbed6ad696e5959982508456332419be9fdeaf29b2e58d0eadc45483cc16963c0446
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immutable@npm:~3.7.4":
|
||||
version: 3.7.6
|
||||
resolution: "immutable@npm:3.7.6"
|
||||
17
frontend/patches/@zip.js__zip.js.patch
Normal file
17
frontend/patches/@zip.js__zip.js.patch
Normal file
@@ -0,0 +1,17 @@
|
||||
diff --git a/lib/zip-core-base.js b/lib/zip-core-base.js
|
||||
index 142155c8c3ab1a6caa7c370e8a2931a954556ca9..61b0fe6efb91312f437750e0002218f218afad8b 100644
|
||||
--- a/lib/zip-core-base.js
|
||||
+++ b/lib/zip-core-base.js
|
||||
@@ -28,12 +28,6 @@
|
||||
|
||||
import { configure } from "./core/configuration.js";
|
||||
|
||||
-try {
|
||||
- configure({ baseURI: import.meta.url });
|
||||
-} catch {
|
||||
- // ignored
|
||||
-}
|
||||
-
|
||||
export * from "./zip-core-reader.js";
|
||||
export * from "./zip-core-writer.js";
|
||||
export {
|
||||
@@ -5947,8 +5947,8 @@
|
||||
"~:spread": "10",
|
||||
"~:color": "rgb(160, 73, 73)",
|
||||
"~:inset": true,
|
||||
"~:offset-x": "10",
|
||||
"~:offset-y": "10"
|
||||
"~:offsetX": "10",
|
||||
"~:offsetY": "10"
|
||||
}
|
||||
],
|
||||
"~:description": "",
|
||||
|
||||
@@ -303,7 +303,7 @@ test.describe("Tokens: Tokens Tab", () => {
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.pressSequentially(".changed");
|
||||
|
||||
await tokensUpdateCreateModal.getByRole("button", { name: "Save" }).click();
|
||||
await tokensUpdateCreateModal.getByRole("button", {name: "Save"}).click();
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
@@ -1070,7 +1070,6 @@ test.describe("Tokens: Apply token", () => {
|
||||
|
||||
// Fill in values for all fields and verify they persist when switching tabs
|
||||
await fontSizeField.fill("16");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
||||
const letterSpacingField =
|
||||
@@ -1239,12 +1238,8 @@ test.describe("Tokens: Apply token", () => {
|
||||
// Fill in the shadow values
|
||||
const offsetXInput = firstShadowFields.getByLabel("X");
|
||||
const offsetYInput = firstShadowFields.getByLabel("Y");
|
||||
const blurInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
const spreadInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Spread",
|
||||
});
|
||||
const blurInput = firstShadowFields.getByLabel("Blur");
|
||||
const spreadInput = firstShadowFields.getByLabel("Spread");
|
||||
|
||||
await offsetXInput.fill("2");
|
||||
await offsetYInput.fill("2");
|
||||
@@ -1265,10 +1260,9 @@ test.describe("Tokens: Apply token", () => {
|
||||
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
|
||||
|
||||
// Verify that a color value was set
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
await expect(colorInput).toHaveValue(/^rgb(.*)$/);
|
||||
const colorInput = firstShadowFields.getByLabel("Color");
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
await expect(firstColorValue).toMatch(/^rgb(.*)$/);
|
||||
|
||||
// Wait for validation to complete
|
||||
await expect(
|
||||
@@ -1286,15 +1280,11 @@ test.describe("Tokens: Apply token", () => {
|
||||
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-0",
|
||||
);
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const colorInput = firstShadowFields.getByLabel("Color");
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// User adds a second shadow
|
||||
const addButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Add Shadow",
|
||||
});
|
||||
const addButton = firstShadowFields.getByTestId("shadow-add-button-0");
|
||||
await addButton.click();
|
||||
|
||||
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
@@ -1303,7 +1293,8 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(secondShadowFields).toBeVisible();
|
||||
|
||||
// User adds a third shadow
|
||||
await addButton.click();
|
||||
const addButton2 = secondShadowFields.getByTestId("shadow-add-button-1");
|
||||
await addButton2.click();
|
||||
|
||||
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-2",
|
||||
@@ -1313,15 +1304,9 @@ test.describe("Tokens: Apply token", () => {
|
||||
// User adds values for the third shadow
|
||||
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
||||
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
||||
const thirdBlurInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Blur",
|
||||
});
|
||||
const thirdSpreadInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Spread",
|
||||
});
|
||||
const thirdColorInput = thirdShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const thirdBlurInput = thirdShadowFields.getByLabel("Blur");
|
||||
const thirdSpreadInput = thirdShadowFields.getByLabel("Spread");
|
||||
const thirdColorInput = thirdShadowFields.getByLabel("Color");
|
||||
|
||||
await thirdOffsetXInput.fill("10");
|
||||
await thirdOffsetYInput.fill("10");
|
||||
@@ -1330,13 +1315,15 @@ test.describe("Tokens: Apply token", () => {
|
||||
await thirdColorInput.fill("#FF0000");
|
||||
|
||||
// User removes the 2nd shadow
|
||||
const removeButton2 = secondShadowFields.getByRole("button", {
|
||||
name: "Remove Shadow",
|
||||
});
|
||||
const removeButton2 = secondShadowFields.getByTestId(
|
||||
"shadow-remove-button-1",
|
||||
);
|
||||
await removeButton2.click();
|
||||
|
||||
// Verify that we have only two shadow fields
|
||||
await expect(thirdShadowFields).not.toBeVisible();
|
||||
// Verify second shadow is removed
|
||||
await expect(
|
||||
secondShadowFields.getByTestId("shadow-add-button-3"),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Verify that the first shadow kept its values
|
||||
const firstOffsetXValue = await firstShadowFields
|
||||
@@ -1346,13 +1333,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const firstBlurValue = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.getByLabel("Blur")
|
||||
.inputValue();
|
||||
const firstSpreadValue = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.getByLabel("Spread")
|
||||
.inputValue();
|
||||
const firstColorValueAfter = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.getByLabel("Color")
|
||||
.inputValue();
|
||||
|
||||
await expect(firstOffsetXValue).toBe("2");
|
||||
@@ -1361,7 +1348,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(firstSpreadValue).toBe("0");
|
||||
await expect(firstColorValueAfter).toBe(firstColorValue);
|
||||
|
||||
// Verify that the second kept its values (after shadow 3)
|
||||
// Verify that the third shadow (now second) kept its values
|
||||
// After removing index 1, the third shadow becomes the second shadow at index 1
|
||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
@@ -1375,13 +1362,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const secondBlurValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.getByLabel("Blur")
|
||||
.inputValue();
|
||||
const secondSpreadValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.getByLabel("Spread")
|
||||
.inputValue();
|
||||
const secondColorValue = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.getByLabel("Color")
|
||||
.inputValue();
|
||||
|
||||
await expect(secondOffsetXValue).toBe("10");
|
||||
@@ -1398,9 +1385,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
||||
"shadow-input-fields-1",
|
||||
);
|
||||
const colorInput = firstShadowFields.getByRole("textbox", {
|
||||
name: "Color",
|
||||
});
|
||||
const colorInput = firstShadowFields.getByLabel("Color");
|
||||
const firstColorValue = await colorInput.inputValue();
|
||||
|
||||
// Switch to reference tab
|
||||
@@ -1428,13 +1413,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const restoredFirstBlur = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.getByLabel("Blur")
|
||||
.inputValue();
|
||||
const restoredFirstSpread = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.getByLabel("Spread")
|
||||
.inputValue();
|
||||
const restoredFirstColor = await firstShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.getByLabel("Color")
|
||||
.inputValue();
|
||||
|
||||
await expect(restoredFirstOffsetX).toBe("2");
|
||||
@@ -1451,13 +1436,13 @@ test.describe("Tokens: Apply token", () => {
|
||||
.getByLabel("Y")
|
||||
.inputValue();
|
||||
const restoredSecondBlur = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Blur" })
|
||||
.getByLabel("Blur")
|
||||
.inputValue();
|
||||
const restoredSecondSpread = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Spread" })
|
||||
.getByLabel("Spread")
|
||||
.inputValue();
|
||||
const restoredSecondColor = await newSecondShadowFields
|
||||
.getByRole("textbox", { name: "Color" })
|
||||
.getByLabel("Color")
|
||||
.inputValue();
|
||||
|
||||
await expect(restoredSecondOffsetX).toBe("10");
|
||||
|
||||
6
frontend/pnpm-workspace.yaml
Normal file
6
frontend/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
patchedDependencies:
|
||||
'@zip.js/zip.js': patches/@zip.js__zip.js.patch
|
||||
|
||||
|
||||
shamefullyHoist: true
|
||||
recursiveInstall: true
|
||||
@@ -368,8 +368,8 @@
|
||||
(let [add-keyed-errors (fn [shadow-result k errors]
|
||||
(update shadow-result :errors concat
|
||||
(map #(assoc % :shadow-key k :shadow-index shadow-index) errors)))
|
||||
parsers {:offset-x parse-sd-token-general-value
|
||||
:offset-y parse-sd-token-general-value
|
||||
parsers {:offsetX parse-sd-token-general-value
|
||||
:offsetY parse-sd-token-general-value
|
||||
:blur parse-sd-token-shadow-blur
|
||||
:spread parse-sd-token-shadow-spread
|
||||
:color parse-sd-token-color-value
|
||||
@@ -389,42 +389,35 @@
|
||||
(defn- parse-sd-token-shadow-value
|
||||
"Parses shadow value and validates it."
|
||||
[value]
|
||||
(let [missing-references
|
||||
(when (string? value)
|
||||
(seq (cto/find-token-value-references value)))]
|
||||
(cond
|
||||
missing-references
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references)]
|
||||
:references missing-references}
|
||||
|
||||
(string? value)
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-shadow value)]}
|
||||
(cond
|
||||
;; Reference value (string)
|
||||
(string? value) {:value value}
|
||||
|
||||
;; Empty value
|
||||
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
|
||||
(nil? value) {:errors [(wte/get-error-code :error.token/empty-input)]}
|
||||
|
||||
;; Invalid value
|
||||
(not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}
|
||||
(not (js/Array.isArray value)) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}
|
||||
|
||||
;; Array of shadows
|
||||
:else
|
||||
(let [converted (js->clj value :keywordize-keys true)
|
||||
:else
|
||||
(let [converted (js->clj value :keywordize-keys true)
|
||||
;; Parse each shadow with its index
|
||||
parsed-shadows (map-indexed
|
||||
(fn [idx shadow-map]
|
||||
(parse-single-shadow shadow-map idx))
|
||||
converted)
|
||||
parsed-shadows (map-indexed
|
||||
(fn [idx shadow-map]
|
||||
(parse-single-shadow shadow-map idx))
|
||||
converted)
|
||||
|
||||
;; Collect all errors from all shadows
|
||||
all-errors (mapcat :errors parsed-shadows)
|
||||
all-errors (mapcat :errors parsed-shadows)
|
||||
|
||||
;; Collect all values from shadows that have values
|
||||
all-values (into [] (keep :value parsed-shadows))]
|
||||
all-values (into [] (keep :value parsed-shadows))]
|
||||
|
||||
(if (seq all-errors)
|
||||
{:errors all-errors
|
||||
:value all-values}
|
||||
{:value all-values})))))
|
||||
(if (seq all-errors)
|
||||
{:errors all-errors
|
||||
:value all-values}
|
||||
{:value all-values}))))
|
||||
|
||||
(defn collect-shadow-errors [token shadow-index]
|
||||
(group-by :shadow-key
|
||||
|
||||
@@ -706,58 +706,53 @@
|
||||
(= 1 (count tree-root)))]
|
||||
|
||||
(cond
|
||||
;; Paste next to selected frame, if selected is itself or of the same size as the copied
|
||||
(and (selected-frame? state)
|
||||
(or (any-same-frame-from-selected? state (keys pobjects))
|
||||
(and only-one-root-shape?
|
||||
(frame-same-size? pobjects (first tree-root)))))
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
parent-id (:parent-id base)
|
||||
paste-x (+ (:width selected-frame-obj) (:x selected-frame-obj) 50)
|
||||
paste-y (:y selected-frame-obj)
|
||||
delta (gpt/subtract (gpt/point paste-x paste-y) orig-pos)]
|
||||
|
||||
[parent-id delta index])
|
||||
|
||||
;; Paste inside selected frame otherwise
|
||||
(selected-frame? state)
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
origin-frame-id (:frame-id first-selected-obj)
|
||||
origin-frame-object (get page-objects origin-frame-id)
|
||||
|
||||
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
|
||||
(min (- (:width frame-object) (:width wrapper))))
|
||||
(if (or (any-same-frame-from-selected? state (keys pobjects))
|
||||
(and only-one-root-shape?
|
||||
(frame-same-size? pobjects (first tree-root))))
|
||||
;; Paste next to selected frame, if selected is itself or of the same size as the copied
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
parent-id (:parent-id base)
|
||||
paste-x (+ (:width selected-frame-obj) (:x selected-frame-obj) 50)
|
||||
paste-y (:y selected-frame-obj)
|
||||
delta (gpt/subtract (gpt/point paste-x paste-y) orig-pos)]
|
||||
|
||||
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
|
||||
(min (- (:height frame-object) (:height wrapper))))
|
||||
[parent-id delta index])
|
||||
|
||||
;; Pasted objects mustn't exceed the selected frame x limit
|
||||
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
|
||||
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
|
||||
(:x frame-object))
|
||||
;; Paste inside selected frame otherwise
|
||||
(let [selected-frame-obj (get page-objects (first page-selected))
|
||||
origin-frame-id (:frame-id first-selected-obj)
|
||||
origin-frame-object (get page-objects origin-frame-id)
|
||||
|
||||
;; Pasted objects mustn't exceed the selected frame y limit
|
||||
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
|
||||
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
|
||||
(:y frame-object))
|
||||
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
|
||||
(min (- (:width frame-object) (:width wrapper))))
|
||||
|
||||
delta (if (= origin-frame-id uuid/zero)
|
||||
;; When the origin isn't in a frame the result is pasted in the center.
|
||||
(gpt/subtract (gsh/shape->center frame-object) (grc/rect->center wrapper))
|
||||
;; When pasting from one frame to another frame the object
|
||||
;; position must be limited to container boundaries. If
|
||||
;; the pasted object doesn't fit we try to:
|
||||
;;
|
||||
;; - Align it to the limits on the x and y axis
|
||||
;; - Respect the distance of the object to the right
|
||||
;; and bottom in the original frame
|
||||
(gpt/point paste-x paste-y))
|
||||
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
|
||||
(min (- (:height frame-object) (:height wrapper))))
|
||||
|
||||
target-index
|
||||
(if (and (ctl/flex-layout? selected-frame-obj) (ctl/reverse? selected-frame-obj))
|
||||
(dec 0) ;; Before the first index 0
|
||||
(count (:shapes selected-frame-obj)))]
|
||||
[frame-id delta target-index])
|
||||
;; Pasted objects mustn't exceed the selected frame x limit
|
||||
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
|
||||
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
|
||||
(:x frame-object))
|
||||
|
||||
;; Pasted objects mustn't exceed the selected frame y limit
|
||||
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
|
||||
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
|
||||
(:y frame-object))
|
||||
|
||||
delta (if (= origin-frame-id uuid/zero)
|
||||
;; When the origin isn't in a frame the result is pasted in the center.
|
||||
(gpt/subtract (gsh/shape->center frame-object) (grc/rect->center wrapper))
|
||||
;; When pasting from one frame to another frame the object
|
||||
;; position must be limited to container boundaries. If
|
||||
;; the pasted object doesn't fit we try to:
|
||||
;;
|
||||
;; - Align it to the limits on the x and y axis
|
||||
;; - Respect the distance of the object to the right
|
||||
;; and bottom in the original frame
|
||||
(gpt/point paste-x paste-y))]
|
||||
[frame-id delta (dec (count (:shapes selected-frame-obj)))]))
|
||||
|
||||
(empty? page-selected)
|
||||
(let [frame-id (ctst/top-nested-frame page-objects position)
|
||||
|
||||
@@ -119,6 +119,21 @@
|
||||
(let [page (dsh/lookup-page state)]
|
||||
(rx/of (update-flow (:id page) flow-id #(assoc % :name name)))))))
|
||||
|
||||
(defn start-rename-flow
|
||||
[id]
|
||||
(dm/assert! (uuid? id))
|
||||
(ptk/reify ::start-rename-flow
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :flow-for-rename] id))))
|
||||
|
||||
(defn end-rename-flow
|
||||
[]
|
||||
(ptk/reify ::end-rename-flow
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :flow-for-rename))))
|
||||
|
||||
;; --- Interactions
|
||||
|
||||
(defn- connected-frame?
|
||||
|
||||
@@ -153,11 +153,11 @@
|
||||
(defn value->shadow
|
||||
"Transform a token shadow value into penpot shadow data structure"
|
||||
[value]
|
||||
(mapv (fn [{:keys [offset-x offset-y blur spread color inset]}]
|
||||
(mapv (fn [{:keys [offsetX offsetY blur spread color inset]}]
|
||||
{:id (random-uuid)
|
||||
:hidden false
|
||||
:offset-x offset-x
|
||||
:offset-y offset-y
|
||||
:offset-x offsetX
|
||||
:offset-y offsetY
|
||||
:blur blur
|
||||
:color (value->color color)
|
||||
:spread spread
|
||||
|
||||
@@ -108,10 +108,6 @@
|
||||
{:error/code :error.style-dictionary/invalid-token-value-shadow-spread
|
||||
:error/fn #(tr "workspace.tokens.shadow-spread-range")}
|
||||
|
||||
:error.style-dictionary/invalid-token-value-shadow
|
||||
{:error/code :error.style-dictionary/invalid-token-value-shadow
|
||||
:error/fn #(tr "workspace.tokens.invalid-token-value-shadow" %)}
|
||||
|
||||
:error/unknown
|
||||
{:error/code :error/unknown
|
||||
:error/fn #(tr "labels.unknown-error")}})
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
:letter-spacing "Letter Spacing"
|
||||
:text-case "Text Case"
|
||||
:text-decoration "Text Decoration"
|
||||
:offset-x "X"
|
||||
:offset-y "Y"
|
||||
:offsetX "X"
|
||||
:offsetY "Y"
|
||||
:blur "Blur"
|
||||
:spread "Spread"
|
||||
:color "Color"
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
(: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]))
|
||||
@@ -47,7 +46,7 @@
|
||||
:disabled disabled)}
|
||||
|
||||
(if (some? icon)
|
||||
[:> icon* {:icon-id icon :class icon-class :aria-hidden true}]
|
||||
[:span {:class icon-class} icon]
|
||||
[:span {:class (stl/css :title-name)} value])
|
||||
|
||||
[:input {:id id
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
[app.config :as cf]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
|
||||
[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*]]
|
||||
@@ -63,7 +62,6 @@
|
||||
:RawSvg raw-svg*
|
||||
:Select select*
|
||||
:Switch switch*
|
||||
:Checkbox checkbox*
|
||||
:Combobox combobox*
|
||||
:Text text*
|
||||
:TabSwitcher tab-switcher*
|
||||
|
||||
@@ -11,18 +11,12 @@
|
||||
%base-button {
|
||||
--button-bg-color: initial;
|
||||
--button-fg-color: initial;
|
||||
|
||||
--button-hover-bg-color: initial;
|
||||
--button-hover-fg-color: initial;
|
||||
|
||||
--button-active-bg-color: initial;
|
||||
--button-active-fg-color: initial;
|
||||
|
||||
--button-disabled-bg-color: initial;
|
||||
--button-disabled-fg-color: initial;
|
||||
|
||||
--button-border-color: var(--button-bg-color);
|
||||
|
||||
--button-focus-inner-ring-color: initial;
|
||||
--button-focus-outer-ring-color: initial;
|
||||
|
||||
@@ -44,10 +38,8 @@
|
||||
--button-fg-color: var(--button-hover-fg-color);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&[aria-pressed="true"] {
|
||||
&:active {
|
||||
--button-bg-color: var(--button-active-bg-color);
|
||||
--button-fg-color: var(--button-active-fg-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@@ -71,7 +63,6 @@
|
||||
--button-hover-fg-color: var(--color-background-secondary);
|
||||
|
||||
--button-active-bg-color: var(--color-accent-tertiary);
|
||||
--button-active-fg-color: var(--color-background-secondary);
|
||||
|
||||
--button-disabled-bg-color: var(--color-accent-primary-muted);
|
||||
--button-disabled-fg-color: var(--color-background-secondary);
|
||||
@@ -81,8 +72,7 @@
|
||||
--button-focus-inner-ring-color: var(--color-background-secondary);
|
||||
--button-focus-outer-ring-color: var(--color-accent-primary);
|
||||
|
||||
&:active,
|
||||
&[aria-pressed="true"] {
|
||||
&:active {
|
||||
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +85,6 @@
|
||||
--button-hover-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-active-bg-color: var(--color-background-quaternary);
|
||||
--button-active-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-disabled-bg-color: transparent;
|
||||
--button-disabled-fg-color: var(--color-foreground-secondary);
|
||||
@@ -114,7 +103,6 @@
|
||||
--button-hover-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-active-bg-color: var(--color-background-quaternary);
|
||||
--button-active-fg-color: var(--color-accent-primary);
|
||||
|
||||
--button-disabled-bg-color: transparent;
|
||||
--button-disabled-fg-color: var(--color-accent-primary-muted);
|
||||
@@ -133,7 +121,6 @@
|
||||
--button-hover-fg-color: var(--color-foreground-primary);
|
||||
|
||||
--button-active-bg-color: var(--color-accent-error);
|
||||
--button-active-fg-color: var(--color-foreground-primary);
|
||||
|
||||
--button-disabled-bg-color: var(--color-background-error);
|
||||
--button-disabled-fg-color: var(--color-accent-error);
|
||||
@@ -143,8 +130,7 @@
|
||||
--button-focus-inner-ring-color: var(--color-background-primary);
|
||||
--button-focus-outer-ring-color: var(--color-accent-primary);
|
||||
|
||||
&:active,
|
||||
&[aria-pressed="true"] {
|
||||
&:active {
|
||||
box-shadow: inset 0 0 #{px2rem(10)} #{px2rem(2)} rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +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.checkbox
|
||||
(: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:checkbox
|
||||
[:map
|
||||
[:id {:optional true} :string]
|
||||
[:label {:optional true} :string]
|
||||
[:checked {:optional true} :boolean]
|
||||
[:on-change {:optional true} fn?]
|
||||
[:disabled {:optional true} :boolean]])
|
||||
|
||||
(mf/defc checkbox*
|
||||
{::mf/schema schema:checkbox}
|
||||
[{:keys [id class label checked on-change disabled] :rest props}]
|
||||
(let [props
|
||||
(mf/spread-props props {:type "checkbox"
|
||||
:class (stl/css :checkbox-input)
|
||||
:id id
|
||||
:checked checked
|
||||
:on-change on-change
|
||||
:disabled disabled})]
|
||||
|
||||
[:div {:class [class (stl/css :checkbox)]}
|
||||
[:label {:for id
|
||||
:class (stl/css :checkbox-label)}
|
||||
[:div {:class (stl/css-case :checkbox-box true
|
||||
:checked checked
|
||||
:disabled disabled)}
|
||||
(when checked
|
||||
[:> icon* {:icon-id i/tick
|
||||
:size "s"}])]
|
||||
|
||||
[:div {:class (stl/css :checkbox-text)} label]
|
||||
|
||||
[:> :input props]]]))
|
||||
@@ -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 */ }
|
||||
|
||||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
import * as Checkbox from "./checkbox.stories";
|
||||
|
||||
<Meta title="Controls/Checkbox" />
|
||||
|
||||
# Checkbox
|
||||
|
||||
The `checkbox*` component is a toggle control. It allows users to switch between boolean states (`false` or `true`).
|
||||
|
||||
<Canvas of={Checkbox.Default} />
|
||||
|
||||
<Canvas of={Checkbox.Checked} />
|
||||
|
||||
## Anatomy
|
||||
|
||||
The checkbox component consists of three main parts:
|
||||
|
||||
- **Label** (optional): the text that describes what the checkbox controls. Clicking on this text also works for toggling.
|
||||
- **Box**: the box which shows the current state. Contains a check if the state is `true`.
|
||||
- **Native element**: the native HTML element which holds the state. It remains hidden to the user.
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### When to Use
|
||||
|
||||
- For boolean settings that take effect immediately.
|
||||
- In preference panels and configuration screens.
|
||||
|
||||
### When Not to Use
|
||||
|
||||
- For actions that require confirmation (use buttons instead).
|
||||
- For multiple choice selections (use radio buttons or select).
|
||||
- For temporary states that need explicit "Apply" action.
|
||||
- For ternary states.
|
||||
@@ -1,86 +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 *;
|
||||
|
||||
.checkbox {
|
||||
--input-checkbox-border-color: var(--color-foreground-secondary);
|
||||
--input-checkbox-border-color-focus: var(--color-accent-primary);
|
||||
--input-checkbox-border-color-hover: var(--color-accent-primary-muted);
|
||||
--input-checkbox-foreground-color: var(--color-foreground-primary);
|
||||
--input-checkbox-background-color: var(--color-background-quaternary);
|
||||
|
||||
--input-checkbox-border-color-checked: var(--color-background-quaternary);
|
||||
--input-checkbox-foreground-color-checked: var(--color-background-primary);
|
||||
--input-checkbox-background-color-checked: var(--color-accent-primary);
|
||||
|
||||
--input-checkbox-foreground-color-disabled: var(--color-background-primary);
|
||||
--input-checkbox-background-color-disabled: var(--color-foreground-secondary);
|
||||
|
||||
--input-checkbox-text-color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sp-l) 1fr 0;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
.checkbox-box {
|
||||
border-color: var(--input-checkbox-border-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
.checkbox-box {
|
||||
border-color: var(--input-checkbox-border-color-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: $sz-16;
|
||||
block-size: $sz-16;
|
||||
border-radius: $br-4;
|
||||
border: $b-1 solid var(--input-checkbox-border-color);
|
||||
color: var(--input-checkbox-foreground-color);
|
||||
background-color: var(--input-checkbox-background-color);
|
||||
|
||||
&.disabled {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
--input-checkbox-border-color: var(--input-checkbox-border-color-checked);
|
||||
--input-checkbox-foreground-color: var(--input-checkbox-foreground-color-checked);
|
||||
--input-checkbox-background-color: var(--input-checkbox-background-color-checked);
|
||||
|
||||
&.disabled {
|
||||
--input-checkbox-foreground-color: var(--input-checkbox-foreground-color-disabled);
|
||||
--input-checkbox-background-color: var(--input-checkbox-background-color-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-text {
|
||||
@include use-typography("body-small");
|
||||
padding-inline-start: var(--sp-s);
|
||||
color: var(--input-checkbox-text-color);
|
||||
}
|
||||
|
||||
.checkbox-input {
|
||||
&:focus {
|
||||
outline: 0;
|
||||
inline-size: 0;
|
||||
block-size: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +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 { Checkbox } = Components;
|
||||
|
||||
export default {
|
||||
title: "Controls/Checkbox",
|
||||
component: Checkbox,
|
||||
argTypes: {
|
||||
label: {
|
||||
control: { type: "text" },
|
||||
description: "Label text displayed next to the checkbox",
|
||||
},
|
||||
checked: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is disabled",
|
||||
},
|
||||
},
|
||||
args: {
|
||||
checked: false,
|
||||
disabled: false,
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
exclude: ["id", "on-change"],
|
||||
},
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
args: {
|
||||
label: "Toggle something",
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const Checked = {
|
||||
args: {
|
||||
label: "Toggle something",
|
||||
checked: true,
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const WithoutLabel = {
|
||||
args: {
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => <Checkbox {...args} />,
|
||||
};
|
||||
|
||||
export const WithLongLabel = {
|
||||
args: {
|
||||
label:
|
||||
"This is a very long label that demonstrates how the checkbox component handles text wrapping and layout when the label content is extensive",
|
||||
disabled: false,
|
||||
},
|
||||
render: ({ ...args }) => (
|
||||
<div style={{ maxWidth: "300px" }}>
|
||||
<Checkbox {...args} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
@@ -26,19 +26,14 @@
|
||||
[:max-length {:optional true} :int]
|
||||
[:variant {:optional true} [:maybe [:enum "seamless" "dense" "comfortable"]]]
|
||||
[:hint-message {:optional true} [:maybe :string]]
|
||||
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]
|
||||
[:hint-formated {:optional true} :boolean]])
|
||||
[:hint-type {:optional true} [:maybe [:enum "hint" "error" "warning"]]]])
|
||||
|
||||
(mf/defc input*
|
||||
{::mf/forward-ref true
|
||||
::mf/schema schema:input}
|
||||
[{:keys [id class label is-optional type max-length variant hint-message hint-type hint-formated] :rest props} ref]
|
||||
[{:keys [id class label is-optional type max-length variant hint-message hint-type] :rest props} ref]
|
||||
(let [id (or id (mf/use-id))
|
||||
variant (d/nilv variant "dense")
|
||||
hint-class (if (and (not= "error" hint-type)
|
||||
hint-formated)
|
||||
(stl/css :hint-formated)
|
||||
"")
|
||||
is-optional (d/nilv is-optional false)
|
||||
type (d/nilv type "text")
|
||||
max-length (d/nilv max-length max-input-length)
|
||||
@@ -61,7 +56,6 @@
|
||||
[:> input-field* props]
|
||||
(when has-hint
|
||||
[:> hint-message* {:id id
|
||||
:class hint-class
|
||||
:message hint-message
|
||||
:type hint-type}])]))
|
||||
|
||||
|
||||
@@ -12,7 +12,3 @@
|
||||
gap: var(--sp-xs);
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.hint-formated {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,6 @@
|
||||
(mf/use-fn
|
||||
(mf/deps disabled)
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(when-not disabled
|
||||
(swap! is-open* not))))
|
||||
|
||||
@@ -53,15 +53,10 @@
|
||||
"true")
|
||||
:aria-describedby (when has-hint
|
||||
(str id "-hint"))
|
||||
:aria-labelledby tooltip-id
|
||||
:type (d/nilv type "text")
|
||||
:id id
|
||||
:max-length (d/nilv max-length max-input-length)})
|
||||
|
||||
props (if (and aria-label (not (some? icon)))
|
||||
(mf/spread-props props
|
||||
{:aria-label aria-label})
|
||||
(mf/spread-props props
|
||||
{:aria-labelledby tooltip-id}))
|
||||
inside-class (stl/css-case :input-wrapper true
|
||||
:has-hint has-hint
|
||||
:hint-type-hint (= hint-type "hint")
|
||||
|
||||
@@ -300,7 +300,6 @@
|
||||
"A collection of all icons"
|
||||
(collect-icons))
|
||||
|
||||
(def ^:private ^:const icon-size-l 32)
|
||||
(def ^:private ^:const icon-size-m 16)
|
||||
(def ^:private ^:const icon-size-s 12)
|
||||
|
||||
@@ -309,7 +308,7 @@
|
||||
[:class {:optional true} [:maybe :string]]
|
||||
[:icon-id [:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:size {:optional true}
|
||||
[:maybe [:enum "s" "m" "l"]]]])
|
||||
[:maybe [:enum "s" "m"]]]])
|
||||
|
||||
(mf/defc icon*
|
||||
{::mf/schema schema:icon}
|
||||
@@ -318,14 +317,10 @@
|
||||
{:class [class (stl/css :icon)]
|
||||
:width icon-size-m
|
||||
:height icon-size-m})
|
||||
|
||||
size-px (cond (= size "l") icon-size-l
|
||||
(= size "s") icon-size-s
|
||||
:else icon-size-m)
|
||||
|
||||
offset (if (or (= size "s") (= size "m"))
|
||||
(/ (- icon-size-m size-px) 2)
|
||||
0)]
|
||||
size-px (if (= size "s")
|
||||
icon-size-s
|
||||
icon-size-m)
|
||||
offset (/ (- icon-size-m size-px) 2)]
|
||||
|
||||
[:> :svg props
|
||||
[:use {:href (dm/str "#icon-" icon-id)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
.swatch {
|
||||
--border-color: var(--color-accent-primary-muted);
|
||||
--border-color: var(--color-background-quaternary);
|
||||
--border-radius: #{$br-4};
|
||||
--border-color-active: var(--color-foreground-primary);
|
||||
--border-color-active-inset: var(--color-background-primary);
|
||||
|
||||
@@ -34,9 +34,10 @@
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(fm/on-input-change form input-name value true))))
|
||||
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:value value})
|
||||
:default-value value})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
padding-bottom: deprecated.$s-16;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-gutter: stable;
|
||||
padding-inline: var(--sp-m);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
.tool-windows {
|
||||
block-size: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,8 @@
|
||||
.inspect-tab-switcher-label {
|
||||
@include use-typography("body-medium");
|
||||
color: var(--color-foreground-primary);
|
||||
flex: 0 1 40%;
|
||||
flex: 0;
|
||||
min-inline-size: fit-content;
|
||||
}
|
||||
|
||||
.inspect-tab-switcher-controls {
|
||||
@@ -150,6 +151,7 @@
|
||||
}
|
||||
|
||||
.inspect-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -157,5 +159,6 @@
|
||||
--tabs-nav-padding-inline-start: 0;
|
||||
--tabs-nav-padding-inline-end: var(--sp-m);
|
||||
|
||||
block-size: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -1,11 +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/_utils.scss" as *;
|
||||
|
||||
.styles-tab {
|
||||
block-size: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
|
||||
}
|
||||
@@ -418,11 +418,11 @@
|
||||
[:& radio-buttons {:selected color-style
|
||||
:on-change toggle-token-color
|
||||
:name "color-style"}
|
||||
[:& radio-button {:icon i/swatches
|
||||
[:& radio-button {:icon deprecated-icon/swatches
|
||||
:value :direct-color
|
||||
:title (tr "labels.color")
|
||||
:id "opt-color"}]
|
||||
[:& radio-button {:icon i/tokens
|
||||
[:& radio-button {:icon deprecated-icon/tokens
|
||||
:value :token-color
|
||||
:title (tr "workspace.colorpicker.color-tokens")
|
||||
:id "opt-token-color"}]])]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.main.ui.workspace.shapes.text.editor
|
||||
(:require
|
||||
["draft-js" :as draft]
|
||||
["@penpot/draft-js" :as draft]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.sidebar.assets.groups :as grp]
|
||||
[app.util.dom :as dom]
|
||||
@@ -566,11 +567,11 @@
|
||||
[:& radio-buttons {:selected (if is-listing-thumbs "grid" "list")
|
||||
:on-change toggle-list-style
|
||||
:name "listing-style"}
|
||||
[:& radio-button {:icon i/view-as-list
|
||||
[:& radio-button {:icon deprecated-icon/view-as-list
|
||||
:value "list"
|
||||
:title (tr "workspace.assets.list-view")
|
||||
:id "opt-list"}]
|
||||
[:& radio-button {:icon i/flex-grid
|
||||
[:& radio-button {:icon deprecated-icon/flex-grid
|
||||
:value "grid"
|
||||
:title (tr "workspace.assets.grid-view")
|
||||
:id "opt-grid"}]]])
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
|
||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layout-container :as layout-container]
|
||||
[app.main.ui.workspace.sidebar.options.page :as page]
|
||||
[app.main.ui.workspace.sidebar.options.shapes.bool :as bool]
|
||||
@@ -215,7 +215,7 @@
|
||||
(case options-mode
|
||||
:prototype
|
||||
[:div {:class (stl/css :element-options :interaction-options)}
|
||||
[:> interactions-menu* {:shape (first shapes)}]]
|
||||
[:& interactions-menu {:shape (first shapes)}]]
|
||||
|
||||
:inspect
|
||||
[:div {:class (stl/css :element-options :inspect-options)}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
@@ -100,10 +99,10 @@
|
||||
:name "frame-orientation"
|
||||
:wide true
|
||||
:class (stl/css :radio-buttons)}
|
||||
[:& radio-button {:icon i/size-vertical
|
||||
[:& radio-button {:icon deprecated-icon/size-vertical
|
||||
:value "vertical"
|
||||
:id "size-vertical"}]
|
||||
[:& radio-button {:icon i/size-horizontal
|
||||
[:& radio-button {:icon deprecated-icon/size-horizontal
|
||||
:value "horizontal"
|
||||
:id "size-horizontal"}]]]))
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -79,22 +78,22 @@
|
||||
:class (stl/css :boolean-radio-btn)
|
||||
:on-change on-change
|
||||
:name "bool-options"}
|
||||
[:& radio-button {:icon i/boolean-union
|
||||
[:& radio-button {:icon deprecated-icon/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
|
||||
[:& radio-button {:icon deprecated-icon/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
|
||||
[:& radio-button {:icon deprecated-icon/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
|
||||
[:& radio-button {:icon deprecated-icon/boolean-exclude
|
||||
:value "exclude"
|
||||
:disabled disabled-bool-btns
|
||||
:title (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.sidebar.options.menus.variants-help-modal]
|
||||
[app.util.debug :as dbg]
|
||||
@@ -797,10 +798,10 @@
|
||||
[:& 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
|
||||
[:& radio-button {:icon deprecated-icon/view-as-list
|
||||
:value "list"
|
||||
:id "swap-opt-list"}]
|
||||
[:& radio-button {:icon i/flex-grid
|
||||
[:& radio-button {:icon deprecated-icon/flex-grid
|
||||
:value "grid"
|
||||
:id "swap-opt-grid"}]]]
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
:data-value "bottom"
|
||||
:on-click on-constraint-button-clicked}
|
||||
[:span {:class (stl/css :resalted-area)}]]]]
|
||||
[:div {:class (stl/css :constraints-selects)}
|
||||
[:div {:class (stl/css :contraints-selects)}
|
||||
[:div {:class (stl/css :horizontal-select) :data-testid "constraint-h-select"}
|
||||
[:& select
|
||||
{:default-value (if (not= constraints-h :multiple) (d/nilv (d/name constraints-h) "scale") "")
|
||||
|
||||
@@ -120,9 +120,7 @@
|
||||
}
|
||||
|
||||
.constraints-selects {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.horizontal-select,
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
[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.foundations.assets.icon :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.util.dom :as dom]
|
||||
@@ -53,29 +52,29 @@
|
||||
: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)
|
||||
deprecated-icon/align-self-row-left
|
||||
deprecated-icon/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)
|
||||
deprecated-icon/align-self-row-center
|
||||
deprecated-icon/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)
|
||||
deprecated-icon/align-self-row-right
|
||||
deprecated-icon/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)
|
||||
deprecated-icon/align-self-row-stretch
|
||||
deprecated-icon/align-self-column-stretch)
|
||||
:title "Align self stretch"
|
||||
:id (dm/str "align-self-stretch-" type)}]]]))
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,51 +4,22 @@
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/mixins.scss" as *;
|
||||
@use "ds/typography.scss" as t;
|
||||
@use "../../../sidebar/common/sidebar.scss" as sidebar;
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.wrapper {
|
||||
.interactions-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
padding-inline-start: var(--sp-m);
|
||||
gap: deprecated.$s-8;
|
||||
padding-left: var(--sp-m);
|
||||
}
|
||||
|
||||
.section {
|
||||
@include sidebar.option-grid-structure;
|
||||
.interaction-options {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.title {
|
||||
grid-column: span 8;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
padding-inline-start: var(--sp-xxs);
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-column: span 8;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
margin-block-start: var(--sp-xs);
|
||||
}
|
||||
|
||||
.content-interactions {
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.help {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxxl);
|
||||
inline-size: $sz-200;
|
||||
padding: var(--sp-xxxl) 0;
|
||||
.help-content {
|
||||
padding: deprecated.$s-32 0;
|
||||
width: deprecated.$s-200;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -56,168 +27,130 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--sp-m);
|
||||
margin-bottom: deprecated.$s-40;
|
||||
gap: deprecated.$s-12;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
@include t.use-typography("body-small");
|
||||
.interactions-help-icon {
|
||||
@include deprecated.flexCenter;
|
||||
width: deprecated.$s-48;
|
||||
height: deprecated.$s-48;
|
||||
border-radius: deprecated.$br-circle;
|
||||
background-color: var(--pill-background-color);
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-32;
|
||||
}
|
||||
}
|
||||
|
||||
.after {
|
||||
@include deprecated.bodySmallTypography;
|
||||
margin-top: deprecated.$s-1;
|
||||
}
|
||||
|
||||
.interactions-help {
|
||||
@include deprecated.bodySmallTypography;
|
||||
text-align: center;
|
||||
color: var(--color-foreground-secondary);
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
inline-size: $sz-48;
|
||||
block-size: $sz-48;
|
||||
.element-set {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.help-icon-inner {
|
||||
color: var(--color-foreground-secondary);
|
||||
inline-size: $sz-32;
|
||||
block-size: $sz-32;
|
||||
}
|
||||
|
||||
.interaction-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.prototype-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: px2rem(1);
|
||||
border-radius: $br-8;
|
||||
padding: var(--sp-s) var(--sp-m);
|
||||
block-size: $sz-32;
|
||||
padding: 0;
|
||||
|
||||
&.double {
|
||||
block-size: $sz-48;
|
||||
.prototype-pill-button {
|
||||
block-size: $sz-48;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.prototype-pill-input:focus) {
|
||||
outline: $b-1 solid var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.prototype-pill-button {
|
||||
&.left {
|
||||
border-end-end-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
}
|
||||
|
||||
&.right {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.prototype-pill-main {
|
||||
display: flex;
|
||||
.interactions-info {
|
||||
flex-grow: 1;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.prototype-pill-center {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
block-size: 100%;
|
||||
padding: 0 var(--sp-s);
|
||||
background-color: var(--color-background-tertiary);
|
||||
.trigger-name {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.prototype-pill-info {
|
||||
display: grid;
|
||||
inline-size: 100%;
|
||||
.action-summary {
|
||||
color: var(--color-foreground-secondary);
|
||||
@include deprecated.textEllipsis;
|
||||
}
|
||||
|
||||
.prototype-pill-input {
|
||||
@include t.use-typography("body-small");
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
flex-grow: 1;
|
||||
margin: var(--sp-xxs) 0;
|
||||
padding: 0 0 0 var(--sp-s);
|
||||
.groups {
|
||||
@include deprecated.flexColumn(deprecated.$s-12);
|
||||
}
|
||||
|
||||
.element-set-options-group-open {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.extended-options {
|
||||
@include deprecated.flexColumn;
|
||||
}
|
||||
|
||||
.property-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
background-color: var(--color-background-tertiary);
|
||||
color: var(--color-foreground-primary);
|
||||
display: grid;
|
||||
inline-size: 100%;
|
||||
row-gap: deprecated.$s-16;
|
||||
margin-block: calc(#{deprecated.$s-16} - #{deprecated.$s-4});
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-quaternary);
|
||||
&:active {
|
||||
background-color: var(--color-background-quaternary);
|
||||
.property-row {
|
||||
@extend .attr-row;
|
||||
height: auto;
|
||||
&.big-row {
|
||||
height: 100%;
|
||||
}
|
||||
.interaction-name {
|
||||
@include deprecated.twoLineTextEllipsis;
|
||||
@include deprecated.bodySmallTypography;
|
||||
padding-left: deprecated.$s-4;
|
||||
width: deprecated.$s-92;
|
||||
margin: auto 0;
|
||||
grid-area: name;
|
||||
color: var(--title-foreground-color);
|
||||
}
|
||||
.select-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-area: content;
|
||||
.easing-select {
|
||||
width: deprecated.$s-156;
|
||||
padding: 0 deprecated.$s-8;
|
||||
.dropdown-upwards {
|
||||
bottom: deprecated.$s-36;
|
||||
width: deprecated.$s-156;
|
||||
top: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--color-background-tertiary);
|
||||
.input-element-wrapper {
|
||||
@extend .input-element;
|
||||
@include deprecated.bodySmallTypography;
|
||||
grid-area: content;
|
||||
}
|
||||
.buttons-wrapper {
|
||||
grid-area: content;
|
||||
.right svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.left svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.up svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
.inputs-wrapper {
|
||||
grid-area: content;
|
||||
@include deprecated.flexRow;
|
||||
.radio-btn {
|
||||
@extend .input-checkbox;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.prototype-pill-name {
|
||||
@include t.use-typography("body-small");
|
||||
@include textEllipsis;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.prototype-pill-description {
|
||||
@include t.use-typography("body-small");
|
||||
@include textEllipsis;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.interaction-row {
|
||||
@include sidebar.option-grid-structure;
|
||||
}
|
||||
|
||||
.interaction-row-label {
|
||||
grid-column: span 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.interaction-row-name {
|
||||
@include twoLineTextEllipsis;
|
||||
@include t.use-typography("body-small");
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.interaction-row-select {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.interaction-row-checkbox {
|
||||
grid-column: 4 / span 5;
|
||||
min-block-size: $sz-32;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.interaction-row-input {
|
||||
grid-column: span 5;
|
||||
}
|
||||
|
||||
.interaction-row-radio {
|
||||
grid-column: 4 / span 5;
|
||||
}
|
||||
|
||||
.interaction-row-position {
|
||||
grid-column: 4 / span 5;
|
||||
.position-btns-wrapper {
|
||||
grid-area: content;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"topleft top topright"
|
||||
@@ -225,30 +158,191 @@
|
||||
"bottomleft bottom bottomright";
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
inline-size: calc($sz-32 * 3);
|
||||
block-size: calc($sz-32 * 3);
|
||||
border-radius: $br-8;
|
||||
width: deprecated.$s-84;
|
||||
height: deprecated.$s-84;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
|
||||
.center {
|
||||
.center-btn {
|
||||
grid-area: center;
|
||||
}
|
||||
.top-left {
|
||||
.top-left-btn {
|
||||
grid-area: topleft;
|
||||
}
|
||||
.top-center {
|
||||
grid-area: top;
|
||||
}
|
||||
.top-right {
|
||||
.top-right-btn {
|
||||
grid-area: topright;
|
||||
}
|
||||
.bottom-left {
|
||||
.top-center-btn {
|
||||
grid-area: top;
|
||||
}
|
||||
.bottom-left-btn {
|
||||
grid-area: bottomleft;
|
||||
}
|
||||
.bottom-center {
|
||||
grid-area: bottom;
|
||||
}
|
||||
.bottom-right {
|
||||
.bottom-right-btn {
|
||||
grid-area: bottomright;
|
||||
}
|
||||
.bottom-center-btn {
|
||||
grid-area: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.direction-btn {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-28;
|
||||
width: deprecated.$s-28;
|
||||
|
||||
&.active {
|
||||
@extend .button-icon-selected;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-option {
|
||||
@extend .input-checkbox;
|
||||
grid-area: content;
|
||||
line-height: 1.2;
|
||||
label {
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.interactions-summary {
|
||||
@extend .asset-element;
|
||||
height: deprecated.$s-44;
|
||||
padding: 0;
|
||||
gap: deprecated.$s-8;
|
||||
|
||||
.remove-btn {
|
||||
@extend .button-tertiary;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.extend-btn {
|
||||
@extend .button-tertiary;
|
||||
--button-tertiary-border-width: var(--expand-button-icon-border-width);
|
||||
height: 100%;
|
||||
width: deprecated.$s-28;
|
||||
border-end-end-radius: 0;
|
||||
border-start-end-radius: 0;
|
||||
padding: 0;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
}
|
||||
position: relative;
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-inline-end: deprecated.$s-1 solid var(--panel-background-color);
|
||||
}
|
||||
&.extended {
|
||||
@extend .button-icon-selected;
|
||||
--button-tertiary-border-width: var(--expand-button-icon-border-width-selected);
|
||||
}
|
||||
}
|
||||
|
||||
.corner-icon {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
width: deprecated.$s-12;
|
||||
height: deprecated.$s-12;
|
||||
}
|
||||
|
||||
.flow-element {
|
||||
@include deprecated.flexRow;
|
||||
}
|
||||
|
||||
.flow-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-2;
|
||||
border-radius: deprecated.$s-8;
|
||||
background-color: var(--input-details-color);
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.flow-name-wrapper {
|
||||
@include deprecated.bodySmallTypography;
|
||||
@include deprecated.focusInput;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: deprecated.$s-4;
|
||||
flex-grow: 1;
|
||||
height: deprecated.$s-32;
|
||||
width: 100%;
|
||||
border-radius: deprecated.$br-8;
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
background-color: var(--input-background-color);
|
||||
border: deprecated.$s-1 solid var(--input-border-color);
|
||||
color: var(--input-foreground-color);
|
||||
.start-flow-btn {
|
||||
@include deprecated.buttonStyle;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-28;
|
||||
padding: 0 deprecated.$s-2 0 deprecated.$s-8;
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
background-color: transparent;
|
||||
svg {
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
&:hover {
|
||||
stroke: var(--input-foreground-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-input {
|
||||
@extend .input-base;
|
||||
@include deprecated.bodySmallTypography;
|
||||
background-color: transparent;
|
||||
height: deprecated.$s-28;
|
||||
}
|
||||
|
||||
.flow-input-wrapper {
|
||||
@include deprecated.bodySmallTypography;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: deprecated.$s-28;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
background-color: transparent;
|
||||
color: var(--input-foreground-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--input-background-color-hover);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-hover);
|
||||
&:active {
|
||||
background-color: var(--input-background-color-hover);
|
||||
.flow-input-wrapper {
|
||||
background-color: var(--input-background-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
background-color: var(--input-background-color-focus);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
&:hover {
|
||||
border: deprecated.$s-1 solid var(--input-border-color-focus);
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
background-color: var(--input-background-color-active);
|
||||
border: deprecated.$s-1 solid var(--input-border-color-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
(defn- dir-icons-refactor
|
||||
[val]
|
||||
(case val
|
||||
:row i/grid-row
|
||||
:row-reverse i/row-reverse
|
||||
:column i/column
|
||||
:column-reverse i/column-reverse))
|
||||
:row deprecated-icon/grid-row
|
||||
:row-reverse deprecated-icon/row-reverse
|
||||
:column deprecated-icon/column
|
||||
:column-reverse deprecated-icon/column-reverse))
|
||||
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
@@ -111,63 +111,63 @@
|
||||
:align-items
|
||||
(if column?
|
||||
(case val
|
||||
:start i/align-items-column-start
|
||||
:end i/align-items-column-end
|
||||
:center i/align-items-column-center)
|
||||
:start deprecated-icon/align-items-column-start
|
||||
:end deprecated-icon/align-items-column-end
|
||||
:center deprecated-icon/align-items-column-center)
|
||||
(case val
|
||||
:start i/align-items-row-start
|
||||
:end i/align-items-row-end
|
||||
:center i/align-items-row-center))
|
||||
:start deprecated-icon/align-items-row-start
|
||||
:end deprecated-icon/align-items-row-end
|
||||
:center deprecated-icon/align-items-row-center))
|
||||
|
||||
:justify-content
|
||||
(if column?
|
||||
(case val
|
||||
:start i/justify-content-column-start
|
||||
:end i/justify-content-column-end
|
||||
:center i/justify-content-column-center
|
||||
:space-around i/justify-content-column-around
|
||||
:space-evenly i/justify-content-column-evenly
|
||||
:space-between i/justify-content-column-between)
|
||||
:start deprecated-icon/justify-content-column-start
|
||||
:end deprecated-icon/justify-content-column-end
|
||||
:center deprecated-icon/justify-content-column-center
|
||||
:space-around deprecated-icon/justify-content-column-around
|
||||
:space-evenly deprecated-icon/justify-content-column-evenly
|
||||
:space-between deprecated-icon/justify-content-column-between)
|
||||
(case val
|
||||
:start i/justify-content-row-start
|
||||
:end i/justify-content-row-end
|
||||
:center i/justify-content-row-center
|
||||
:space-around i/justify-content-row-around
|
||||
:space-evenly i/justify-content-row-evenly
|
||||
:space-between i/justify-content-row-between))
|
||||
:start deprecated-icon/justify-content-row-start
|
||||
:end deprecated-icon/justify-content-row-end
|
||||
:center deprecated-icon/justify-content-row-center
|
||||
:space-around deprecated-icon/justify-content-row-around
|
||||
:space-evenly deprecated-icon/justify-content-row-evenly
|
||||
:space-between deprecated-icon/justify-content-row-between))
|
||||
|
||||
:align-content
|
||||
(if column?
|
||||
(case val
|
||||
:start i/align-content-column-start
|
||||
:end i/align-content-column-end
|
||||
:center i/align-content-column-center
|
||||
:space-around i/align-content-column-around
|
||||
:space-evenly i/align-content-column-evenly
|
||||
:space-between i/align-content-column-between
|
||||
:start deprecated-icon/align-content-column-start
|
||||
:end deprecated-icon/align-content-column-end
|
||||
:center deprecated-icon/align-content-column-center
|
||||
:space-around deprecated-icon/align-content-column-around
|
||||
:space-evenly deprecated-icon/align-content-column-evenly
|
||||
:space-between deprecated-icon/align-content-column-between
|
||||
:stretch nil)
|
||||
|
||||
(case val
|
||||
:start i/align-content-row-start
|
||||
:end i/align-content-row-end
|
||||
:center i/align-content-row-center
|
||||
:space-around i/align-content-row-around
|
||||
:space-evenly i/align-content-row-evenly
|
||||
:space-between i/align-content-row-between
|
||||
:start deprecated-icon/align-content-row-start
|
||||
:end deprecated-icon/align-content-row-end
|
||||
:center deprecated-icon/align-content-row-center
|
||||
:space-around deprecated-icon/align-content-row-around
|
||||
:space-evenly deprecated-icon/align-content-row-evenly
|
||||
:space-between deprecated-icon/align-content-row-between
|
||||
:stretch nil))
|
||||
|
||||
:align-self
|
||||
(if column?
|
||||
(case val
|
||||
:auto i/remove
|
||||
:start i/align-self-row-left
|
||||
:end i/align-self-row-right
|
||||
:center i/align-self-row-center)
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-row-left
|
||||
:end deprecated-icon/align-self-row-right
|
||||
:center deprecated-icon/align-self-row-center)
|
||||
(case val
|
||||
:auto i/remove
|
||||
:start i/align-self-column-top
|
||||
:end i/align-self-column-bottom
|
||||
:center i/align-self-column-center))))
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-column-top
|
||||
:end deprecated-icon/align-self-column-bottom
|
||||
:center deprecated-icon/align-self-column-center))))
|
||||
|
||||
(defn get-layout-grid-icon
|
||||
[type val ^boolean column?]
|
||||
@@ -175,32 +175,32 @@
|
||||
:align-items
|
||||
(if column?
|
||||
(case val
|
||||
:auto i/remove
|
||||
:start i/align-self-row-left
|
||||
:end i/align-self-row-right
|
||||
:center i/align-self-row-center)
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-row-left
|
||||
:end deprecated-icon/align-self-row-right
|
||||
:center deprecated-icon/align-self-row-center)
|
||||
(case val
|
||||
:auto i/remove
|
||||
:start i/align-self-column-top
|
||||
:end i/align-self-column-bottom
|
||||
:center i/align-self-column-center))
|
||||
:auto deprecated-icon/remove-icon
|
||||
:start deprecated-icon/align-self-column-top
|
||||
:end deprecated-icon/align-self-column-bottom
|
||||
:center deprecated-icon/align-self-column-center))
|
||||
|
||||
:justify-items
|
||||
(if (not column?)
|
||||
(case val
|
||||
:start i/align-content-column-start
|
||||
:center i/align-content-column-center
|
||||
:end i/align-content-column-end
|
||||
:space-around i/align-content-column-around
|
||||
:space-between i/align-content-column-between
|
||||
:stretch i/align-content-column-stretch)
|
||||
:start deprecated-icon/align-content-column-start
|
||||
:center deprecated-icon/align-content-column-center
|
||||
:end deprecated-icon/align-content-column-end
|
||||
:space-around deprecated-icon/align-content-column-around
|
||||
:space-between deprecated-icon/align-content-column-between
|
||||
:stretch deprecated-icon/align-content-column-stretch)
|
||||
(case val
|
||||
:start i/align-content-row-start
|
||||
:center i/align-content-row-center
|
||||
:end i/align-content-row-end
|
||||
:space-around i/align-content-row-around
|
||||
:space-between i/align-content-row-between
|
||||
:stretch i/align-content-row-stretch))))
|
||||
:start deprecated-icon/align-content-row-start
|
||||
:center deprecated-icon/align-content-row-center
|
||||
:end deprecated-icon/align-content-row-end
|
||||
:space-around deprecated-icon/align-content-row-around
|
||||
:space-between deprecated-icon/align-content-row-between
|
||||
:stretch deprecated-icon/align-content-row-stretch))))
|
||||
|
||||
(mf/defc direction-row-flex
|
||||
{::mf/props :obj
|
||||
|
||||
@@ -455,10 +455,10 @@
|
||||
:name "frame-orientation"
|
||||
:wide true
|
||||
:class (stl/css :radio-buttons)}
|
||||
[:& radio-button {:icon i/size-vertical
|
||||
[:& radio-button {:icon deprecated-icon/size-vertical
|
||||
:value "vert"
|
||||
:id "size-vertical"}]
|
||||
[:& radio-button {:icon i/size-horizontal
|
||||
[:& radio-button {:icon deprecated-icon/size-horizontal
|
||||
:value "horiz"
|
||||
:id "size-horizontal"}]]
|
||||
[:> icon-button*
|
||||
|
||||
@@ -53,19 +53,19 @@
|
||||
[:& radio-button {:value "left"
|
||||
:id "text-align-left"
|
||||
:title (tr "workspace.options.text-options.text-align-left")
|
||||
:icon i/text-align-left}]
|
||||
:icon deprecated-icon/text-align-left}]
|
||||
[:& radio-button {:value "center"
|
||||
:id "text-align-center"
|
||||
:title (tr "workspace.options.text-options.text-align-center")
|
||||
:icon i/text-align-center}]
|
||||
:icon deprecated-icon/text-align-center}]
|
||||
[:& radio-button {:value "right"
|
||||
:id "text-align-right"
|
||||
:title (tr "workspace.options.text-options.text-align-right")
|
||||
:icon i/text-align-right}]
|
||||
:icon deprecated-icon/text-align-right}]
|
||||
[:& radio-button {:value "justify"
|
||||
:id "text-align-justify"
|
||||
:title (tr "workspace.options.text-options.text-align-justify")
|
||||
:icon i/text-justify}]]]))
|
||||
:icon deprecated-icon/text-justify}]]]))
|
||||
|
||||
(mf/defc text-direction-options
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
@@ -88,12 +88,12 @@
|
||||
:type "checkbox"
|
||||
:id "ltr-text-direction"
|
||||
:title (tr "workspace.options.text-options.direction-ltr")
|
||||
:icon i/text-ltr}]
|
||||
:icon deprecated-icon/text-ltr}]
|
||||
[:& radio-button {:value "rtl"
|
||||
:type "checkbox"
|
||||
:id "rtl-text-direction"
|
||||
:title (tr "workspace.options.text-options.direction-rtl")
|
||||
:icon i/text-rtl}]]]))
|
||||
:icon deprecated-icon/text-rtl}]]]))
|
||||
|
||||
(mf/defc vertical-align
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
@@ -113,15 +113,15 @@
|
||||
[:& radio-button {:value "top"
|
||||
:id "vertical-text-align-top"
|
||||
:title (tr "workspace.options.text-options.align-top")
|
||||
:icon i/text-top}]
|
||||
:icon deprecated-icon/text-top}]
|
||||
[:& radio-button {:value "center"
|
||||
:id "vertical-text-align-center"
|
||||
:title (tr "workspace.options.text-options.align-middle")
|
||||
:icon i/text-middle}]
|
||||
:icon deprecated-icon/text-middle}]
|
||||
[:& radio-button {:value "bottom"
|
||||
:id "vertical-text-align-bottom"
|
||||
:title (tr "workspace.options.text-options.align-bottom")
|
||||
:icon i/text-bottom}]]]))
|
||||
:icon deprecated-icon/text-bottom}]]]))
|
||||
|
||||
(mf/defc grow-options
|
||||
[{:keys [ids values on-blur] :as props}]
|
||||
@@ -150,15 +150,15 @@
|
||||
[:& radio-button {:value "fixed"
|
||||
:id "text-fixed-grow"
|
||||
:title (tr "workspace.options.text-options.grow-fixed")
|
||||
:icon i/text-fixed}]
|
||||
:icon deprecated-icon/text-fixed}]
|
||||
[:& radio-button {:value "auto-width"
|
||||
:id "text-auto-width-grow"
|
||||
:title (tr "workspace.options.text-options.grow-auto-width")
|
||||
:icon i/text-auto-width}]
|
||||
:icon deprecated-icon/text-auto-width}]
|
||||
[:& radio-button {:value "auto-height"
|
||||
:id "text-auto-height-grow"
|
||||
:title (tr "workspace.options.text-options.grow-auto-height")
|
||||
:icon i/text-auto-height}]]]))
|
||||
:icon deprecated-icon/text-auto-height}]]]))
|
||||
|
||||
(mf/defc text-decoration-options
|
||||
[{:keys [values on-change on-blur] :as props}]
|
||||
@@ -180,12 +180,12 @@
|
||||
:type "checkbox"
|
||||
:id "underline-text-decoration"
|
||||
:title (tr "workspace.options.text-options.underline" (sc/get-tooltip :underline))
|
||||
:icon i/text-underlined}]
|
||||
:icon deprecated-icon/text-underlined}]
|
||||
[:& radio-button {:value "line-through"
|
||||
:type "checkbox"
|
||||
:id "line-through-text-decoration"
|
||||
:title (tr "workspace.options.text-options.strikethrough" (sc/get-tooltip :line-through))
|
||||
:icon i/text-stroked}]]]))
|
||||
:icon deprecated-icon/text-stroked}]]]))
|
||||
|
||||
(mf/defc text-menu
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
[app.main.ui.components.search-bar :refer [search-bar*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[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]]
|
||||
@@ -419,17 +418,17 @@
|
||||
[:& radio-buttons {:selected text-transform
|
||||
:on-change handle-change
|
||||
:name "text-transform"}
|
||||
[:& radio-button {:icon i/text-uppercase
|
||||
[:& radio-button {:icon deprecated-icon/text-uppercase
|
||||
:type "checkbox"
|
||||
:title (tr "inspect.attributes.typography.text-transform.uppercase")
|
||||
:value "uppercase"
|
||||
:id "text-transform-uppercase"}]
|
||||
[:& radio-button {:icon i/text-mixed
|
||||
[:& radio-button {:icon deprecated-icon/text-mixed
|
||||
:type "checkbox"
|
||||
:value "capitalize"
|
||||
:title (tr "inspect.attributes.typography.text-transform.capitalize")
|
||||
:id "text-transform-capitalize"}]
|
||||
[:& radio-button {:icon i/text-lowercase
|
||||
[:& radio-button {:icon deprecated-icon/text-lowercase
|
||||
:type "checkbox"
|
||||
:title (tr "inspect.attributes.typography.text-transform.lowercase")
|
||||
:value "lowercase"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
|
||||
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||
|
||||
[:resolved-value ::sm/any]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
@@ -80,7 +82,7 @@
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
(mf/with-memo [tokens]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
@@ -168,9 +170,7 @@
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
(tr "workspace.tokens.create-token" token-type)]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
@@ -187,7 +187,7 @@
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token*
|
||||
[:> form-input-token*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.create.color-input-token :refer [color-input-token*]]
|
||||
[app.main.ui.workspace.tokens.management.create.form-color-input-token :refer [form-color-input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -51,7 +51,8 @@
|
||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||
|
||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||
[:color-result {:optional true} ::sm/any]
|
||||
|
||||
[:resolved-value ::sm/any]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
@@ -81,7 +82,7 @@
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
(mf/with-memo [tokens]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
@@ -169,9 +170,7 @@
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
(tr "workspace.tokens.create-token" token-type)]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
@@ -188,7 +187,7 @@
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> color-input-token*
|
||||
[:> form-color-input-token*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.color-input-token
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.colors :as color]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as cl]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.colorpicker :as colorpicker]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(defn- hex->color-obj
|
||||
[hex]
|
||||
(when-let [tc (tinycolor/valid-color hex)]
|
||||
(let [hex (tinycolor/->hex-string tc)
|
||||
alpha (tinycolor/alpha tc)
|
||||
[r g b] (cl/hex->rgb hex)
|
||||
[h s v] (cl/hex->hsv hex)]
|
||||
{:hex hex
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:alpha alpha})))
|
||||
|
||||
(mf/defc ramp*
|
||||
[{:keys [color on-change]}]
|
||||
(let [wrapper-node-ref (mf/use-ref nil)
|
||||
dragging-ref (mf/use-ref false)
|
||||
|
||||
on-start-drag
|
||||
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
|
||||
|
||||
on-finish-drag
|
||||
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
|
||||
|
||||
internal-color*
|
||||
(mf/use-state #(hex->color-obj color))
|
||||
|
||||
internal-color
|
||||
(deref internal-color*)
|
||||
|
||||
on-change'
|
||||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
(fn [{:keys [hex alpha] :as selector-color}]
|
||||
(let [dragging? (mf/ref-val dragging-ref)]
|
||||
(when-not (and dragging? hex)
|
||||
(reset! internal-color* selector-color)
|
||||
(on-change hex alpha)))))]
|
||||
(mf/use-effect
|
||||
(mf/deps color)
|
||||
(fn []
|
||||
;; Update internal color when user changes input value
|
||||
(when-let [color (tinycolor/valid-color color)]
|
||||
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
|
||||
(reset! internal-color* (hex->color-obj color))))))
|
||||
|
||||
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
|
||||
[:div {:ref wrapper-node-ref}
|
||||
[:> ramp-selector*
|
||||
{:color internal-color
|
||||
:on-start-drag on-start-drag
|
||||
:on-finish-drag on-finish-drag
|
||||
:on-change on-change'}]]))
|
||||
|
||||
(mf/defc color-input-token*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
color-resolved
|
||||
(get-in @form [:data :color-result] "")
|
||||
|
||||
|
||||
valid-color (or (tinycolor/valid-color value)
|
||||
(tinycolor/valid-color color-resolved))
|
||||
|
||||
profile (mf/deref refs/profile)
|
||||
|
||||
default-bullet-color
|
||||
(case (:theme profile)
|
||||
"light"
|
||||
color/background-quaternary-light
|
||||
color/background-quaternary)
|
||||
hex
|
||||
(if valid-color
|
||||
(tinycolor/->hex-string (tinycolor/valid-color valid-color))
|
||||
default-bullet-color)
|
||||
|
||||
alpha
|
||||
(if (tinycolor/valid-color valid-color)
|
||||
(tinycolor/alpha (tinycolor/valid-color valid-color))
|
||||
1)
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (:value token)]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
color-ramp-open* (mf/use-state false)
|
||||
color-ramp-open? (deref color-ramp-open*)
|
||||
|
||||
on-click-swatch
|
||||
(mf/use-fn
|
||||
(mf/deps color-ramp-open?)
|
||||
(fn []
|
||||
(let [open? (not color-ramp-open?)]
|
||||
(reset! color-ramp-open* open?))))
|
||||
|
||||
swatch
|
||||
(mf/html
|
||||
[:> swatch*
|
||||
{:background {:color hex :opacity alpha}
|
||||
:show-tooltip false
|
||||
:data-testid "token-form-color-bullet"
|
||||
:class (stl/css :slot-start)
|
||||
:on-click on-click-swatch}])
|
||||
|
||||
on-change-value
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name value)
|
||||
(fn [hex alpha]
|
||||
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
|
||||
prev-input-color (some-> value
|
||||
(tinycolor/valid-color))
|
||||
;; If the input is a reference we will take the format from the computed value
|
||||
prev-computed-color (when-not prev-input-color
|
||||
(some-> value (tinycolor/valid-color)))
|
||||
prev-format (some-> (or prev-input-color prev-computed-color)
|
||||
(tinycolor/color-format))
|
||||
to-rgba? (and
|
||||
(< alpha 1)
|
||||
(or (= prev-format "hex") (not prev-format)))
|
||||
to-hex? (and (not prev-format) (= alpha 1))
|
||||
format (cond
|
||||
to-rgba? "rgba"
|
||||
to-hex? "hex"
|
||||
prev-format prev-format
|
||||
:else "hex")
|
||||
color-value (-> (tinycolor/valid-color hex)
|
||||
(tinycolor/set-alpha (or alpha 1))
|
||||
(tinycolor/->string format))]
|
||||
(when (not= value color-value)
|
||||
(fm/on-input-change form input-name color-value true)
|
||||
(rx/push! resolve-stream color-value)))))
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [raw-value (-> event dom/get-target dom/get-input-value)
|
||||
value (if (tinycolor/hex-without-hash-prefix? raw-value)
|
||||
(dm/str "#" raw-value)
|
||||
raw-value)]
|
||||
(fm/on-input-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
;; TODO: Review this value vs default-value
|
||||
:value (or value "")
|
||||
:hint-message (:message hint)
|
||||
:variant "comfortable"
|
||||
:slot-start swatch
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(let [touched? (get-in @form [:touched input-name])]
|
||||
(when touched?
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors input-name] {:message error})
|
||||
(swap! form assoc-in [:data :color-result] "")
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))]
|
||||
(swap! form update :extra-errors dissoc input-name)
|
||||
(swap! form assoc-in [:data :color-result] value)
|
||||
(reset! hint* {:message message :type "hint"}))))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:*
|
||||
[:> input* props]
|
||||
|
||||
(when color-ramp-open?
|
||||
[:> ramp* {:color value :on-change on-change-value}])]))
|
||||
|
||||
(defn- on-composite-indexed-input-token-change
|
||||
([form field index value composite-type]
|
||||
(on-composite-indexed-input-token-change form field index value composite-type false))
|
||||
([form field index value composite-type trim?]
|
||||
(letfn [(clean-errors [errors]
|
||||
(-> errors
|
||||
(dissoc field)
|
||||
(not-empty)))]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value composite-type index field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
(mf/defc color-input-token-indexed*
|
||||
[{:keys [name tokens token index composite-type] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value composite-type index input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data :value composite-type index input-name] "")
|
||||
|
||||
color-resolved
|
||||
(get-in @form [:data :value composite-type index :color-result] "")
|
||||
|
||||
valid-color (or (tinycolor/valid-color value)
|
||||
(tinycolor/valid-color color-resolved))
|
||||
profile (mf/deref refs/profile)
|
||||
|
||||
default-bullet-color
|
||||
(case (:theme profile)
|
||||
"light"
|
||||
color/background-quaternary-light
|
||||
color/background-quaternary)
|
||||
|
||||
hex
|
||||
(if valid-color
|
||||
(tinycolor/->hex-string (tinycolor/valid-color valid-color))
|
||||
default-bullet-color)
|
||||
|
||||
alpha
|
||||
(if (tinycolor/valid-color valid-color)
|
||||
(tinycolor/alpha (tinycolor/valid-color valid-color))
|
||||
1)
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get-in token [:value composite-type index input-name])]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
color-ramp-open* (mf/use-state false)
|
||||
color-ramp-open? (deref color-ramp-open*)
|
||||
|
||||
on-click-swatch
|
||||
(mf/use-fn
|
||||
(mf/deps color-ramp-open?)
|
||||
(fn []
|
||||
(let [open? (not color-ramp-open?)]
|
||||
(reset! color-ramp-open* open?))))
|
||||
|
||||
swatch
|
||||
(mf/html
|
||||
[:> swatch*
|
||||
{:background {:color hex :opacity alpha}
|
||||
:show-tooltip false
|
||||
:data-testid "token-form-color-bullet"
|
||||
:class (stl/css :slot-start)
|
||||
:on-click on-click-swatch}])
|
||||
|
||||
on-change-value
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name value index)
|
||||
(fn [hex alpha]
|
||||
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
|
||||
prev-input-color (some-> value
|
||||
(tinycolor/valid-color))
|
||||
;; If the input is a reference we will take the format from the computed value
|
||||
prev-computed-color (when-not prev-input-color
|
||||
(some-> value (tinycolor/valid-color)))
|
||||
prev-format (some-> (or prev-input-color prev-computed-color)
|
||||
(tinycolor/color-format))
|
||||
to-rgba? (and
|
||||
(< alpha 1)
|
||||
(or (= prev-format "hex") (not prev-format)))
|
||||
to-hex? (and (not prev-format) (= alpha 1))
|
||||
format (cond
|
||||
to-rgba? "rgba"
|
||||
to-hex? "hex"
|
||||
prev-format prev-format
|
||||
:else "hex")
|
||||
color-value (-> (tinycolor/valid-color hex)
|
||||
(tinycolor/set-alpha (or alpha 1))
|
||||
(tinycolor/->string format))]
|
||||
(when (not= value color-value)
|
||||
(on-composite-indexed-input-token-change form input-name index color-value composite-type true)
|
||||
(rx/push! resolve-stream color-value)))))
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name index)
|
||||
(fn [event]
|
||||
(let [raw-value (-> event dom/get-target dom/get-input-value)
|
||||
value (if (tinycolor/hex-without-hash-prefix? raw-value)
|
||||
(dm/str "#" raw-value)
|
||||
raw-value)]
|
||||
(on-composite-indexed-input-token-change form input-name index value composite-type true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:value (or value "")
|
||||
:hint-message (:message hint)
|
||||
:slot-start swatch
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if error
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name index composite-type]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
(assoc error :message ((:error/fn error) (:error/value error)))))))
|
||||
|
||||
(rx/subs!
|
||||
(fn [{:keys [error value]}]
|
||||
(cond
|
||||
(and error (str/empty? (:error/value error)))
|
||||
(do
|
||||
(swap! form update-in [:errors :value composite-type index] dissoc input-name)
|
||||
(swap! form update-in [:data :value composite-type index] dissoc input-name)
|
||||
(swap! form assoc-in [:data :value composite-type index :color-result] "")
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(reset! hint* {}))
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value composite-type index input-name] {:message error'})
|
||||
(swap! form assoc-in [:data :value composite-type index :color-result] "")
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
|
||||
input-value (get-in @form [:data :value composite-type index input-name] "")]
|
||||
(swap! form update :errors dissoc :value)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(swap! form assoc-in [:data :value composite-type index :color-result] (dwtf/format-token-value value))
|
||||
(if (= input-value (str value))
|
||||
(reset! hint* {})
|
||||
(reset! hint* {:message message :type "hint"})))))))]
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:*
|
||||
[:> input* props]
|
||||
|
||||
(when color-ramp-open?
|
||||
[:> ramp* {:color value :on-change on-change-value}])]))
|
||||
@@ -1,298 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.combobox-token-fonts
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value (cto/split-font-family value)
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(mf/defc font-picker-combobox*
|
||||
[{:keys [token tokens name] :rest props}]
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
font (fonts/find-font-family value)
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (:value token)]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
font-selector-open* (mf/use-state false)
|
||||
font-selector-open? (deref font-selector-open*)
|
||||
|
||||
on-click-dropdown-button
|
||||
(mf/use-fn
|
||||
(mf/deps font-selector-open?)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(reset! font-selector-open* (not font-selector-open?))))
|
||||
|
||||
font-selector-button
|
||||
(mf/html
|
||||
[:> icon-button*
|
||||
{:on-click on-click-dropdown-button
|
||||
:aria-label (tr "workspace.tokens.token-font-family-select")
|
||||
:icon i/arrow-down
|
||||
:variant "action"
|
||||
:type "button"}])
|
||||
|
||||
on-close-font-selector
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! font-selector-open* false)))
|
||||
|
||||
on-select-font
|
||||
(mf/use-fn
|
||||
(mf/deps font)
|
||||
(fn [{:keys [family] :as font}]
|
||||
(when (not= value family)
|
||||
(fm/on-input-change form input-name family true)
|
||||
(rx/push! resolve-stream family))))
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(fm/on-input-change form input-name value false)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:value (or value "")
|
||||
:hint-message (:message hint)
|
||||
:slot-end font-selector-button
|
||||
:variant "comfortable"
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name touched?]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(when touched?
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors input-name] {:message error})
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :extra-errors dissoc input-name)
|
||||
(reset! hint* {:message message :type "hint"})))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:*
|
||||
[:> input* props]
|
||||
(when font-selector-open?
|
||||
[:div {:class (stl/css :font-select-wrapper)}
|
||||
[:> font-selector* {:current-font font
|
||||
:on-select on-select-font
|
||||
:on-close on-close-font-selector
|
||||
:full-size true}]])]))
|
||||
|
||||
(defn- on-composite-combobox-token-change
|
||||
([form field value]
|
||||
(on-composite-combobox-token-change form field value false))
|
||||
([form field value trim?]
|
||||
(letfn [(clean-errors [errors]
|
||||
(-> errors
|
||||
(dissoc field)
|
||||
(not-empty)))]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
(mf/defc font-picker-composite-combobox*
|
||||
[{:keys [token tokens name] :rest props}]
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data :value input-name] "")
|
||||
|
||||
font (fonts/find-font-family value)
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get-in token [:value input-name])]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
font-selector-open* (mf/use-state false)
|
||||
font-selector-open? (deref font-selector-open*)
|
||||
|
||||
on-click-dropdown-button
|
||||
(mf/use-fn
|
||||
(mf/deps font-selector-open?)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(reset! font-selector-open* (not font-selector-open?))))
|
||||
|
||||
font-selector-button
|
||||
(mf/html
|
||||
[:> icon-button*
|
||||
{:on-click on-click-dropdown-button
|
||||
:aria-label (tr "workspace.tokens.token-font-family-select")
|
||||
:icon i/arrow-down
|
||||
:variant "action"
|
||||
:type "button"}])
|
||||
|
||||
on-close-font-selector
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! font-selector-open* false)))
|
||||
|
||||
on-select-font
|
||||
(mf/use-fn
|
||||
(mf/deps font)
|
||||
(fn [{:keys [family] :as font}]
|
||||
(when (not= value family)
|
||||
(on-composite-combobox-token-change form input-name family true)
|
||||
(rx/push! resolve-stream family))))
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(on-composite-combobox-token-change form input-name value false)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:value (or value "")
|
||||
:hint-message (:message hint)
|
||||
:slot-end font-selector-button
|
||||
:variant "comfortable"
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if error
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs!
|
||||
(fn [{:keys [error value]}]
|
||||
(cond
|
||||
(and error (str/empty? (:error/value error)))
|
||||
(do
|
||||
(swap! form update-in [:errors :value] dissoc input-name)
|
||||
(swap! form update-in [:data :value] dissoc input-name)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(reset! hint* {}))
|
||||
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)
|
||||
input-value (get-in @form [:data :value input-name] "")]
|
||||
(swap! form update :errors dissoc :value)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(if (or (empty? value) (= input-value value))
|
||||
(reset! hint* {})
|
||||
(reset! hint* {:message message :type "hint"})))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:*
|
||||
[:> input* props]
|
||||
(when font-selector-open?
|
||||
[:div {:class (stl/css :font-select-wrapper)}
|
||||
[:> font-selector* {:current-font font
|
||||
:on-select on-select-font
|
||||
:on-close on-close-font-selector
|
||||
:full-size true}]])]))
|
||||
@@ -1,14 +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/_utils.scss" as *;
|
||||
|
||||
.font-select-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: px2rem(115);
|
||||
top: px2rem(54);
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
|
||||
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||
|
||||
[:resolved-value ::sm/any]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
@@ -80,7 +82,7 @@
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
(mf/with-memo [tokens]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
@@ -167,9 +169,7 @@
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
(tr "workspace.tokens.create-token" token-type)]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
@@ -186,7 +186,7 @@
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token*
|
||||
[:> form-input-token*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.font-family
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.create.combobox-token-fonts :refer [font-picker-combobox*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- make-schema
|
||||
[tokens-tree]
|
||||
(sm/schema
|
||||
[:and
|
||||
[:map
|
||||
[:name
|
||||
[:and
|
||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
(sm/update-properties cto/token-name-ref assoc :error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||
|
||||
[:value ::sm/text]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
[:fn {:error/field :value
|
||||
:error/fn #(tr "workspace.tokens.self-reference")}
|
||||
(fn [{:keys [name value]}]
|
||||
(when (and name value)
|
||||
(nil? (cto/token-value-self-reference? name value))))]]))
|
||||
|
||||
|
||||
(mf/defc form*
|
||||
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
|
||||
|
||||
(let [token
|
||||
(mf/with-memo [token]
|
||||
(if token
|
||||
(update token :value cto/join-font-family)
|
||||
{:type :font-family}))
|
||||
|
||||
token-type
|
||||
(get token :type)
|
||||
|
||||
token-properties
|
||||
(dwta/get-token-properties token)
|
||||
|
||||
token-title (str/lower (:title token-properties))
|
||||
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
(cond-> tokens
|
||||
(and (:name token) (:value token))
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [tokens-tree-in-selected-set]
|
||||
(make-schema tokens-tree-in-selected-set))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
{:name (:name token "")
|
||||
:value (:value token "")
|
||||
:description (:description token "")})
|
||||
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-cancel
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)))
|
||||
|
||||
on-delete-token
|
||||
(mf/use-fn
|
||||
(mf/deps selected-token-set-id token)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)
|
||||
(st/emit! (dwtl/delete-token selected-token-set-id (:id token)))))
|
||||
|
||||
handle-key-down-delete
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete-token)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-delete-token e))))
|
||||
|
||||
handle-key-down-cancel
|
||||
(mf/use-fn
|
||||
(mf/deps on-cancel)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-cancel e))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(mf/deps validate-token token tokens token-type)
|
||||
(fn [form _event]
|
||||
(let [name (get-in @form [:clean-data :name])
|
||||
description (get-in @form [:clean-data :description])
|
||||
value (get-in @form [:clean-data :value])]
|
||||
(->> (validate-token {:token-value value
|
||||
:token-name name
|
||||
:token-description description
|
||||
:prev-token token
|
||||
:tokens tokens})
|
||||
(rx/subs!
|
||||
(fn [valid-token]
|
||||
(st/emit!
|
||||
(if is-create
|
||||
(dwtl/create-token (ctob/make-token {:name name
|
||||
:type token-type
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
|
||||
(dwtl/update-token (:id token)
|
||||
{:name name
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide))))))))]
|
||||
|
||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
:name :name
|
||||
:label (tr "workspace.tokens.token-name")
|
||||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:auto-focus true}]
|
||||
|
||||
(when (and warning-name-change? (= action "edit"))
|
||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
||||
[:> context-notification*
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> font-picker-combobox*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
:token token
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-description"
|
||||
:name :description
|
||||
:label (tr "workspace.tokens.token-description")
|
||||
:placeholder (tr "workspace.tokens.token-description")
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:is-optional true}]]
|
||||
|
||||
[:div {:class (stl/css-case :button-row true
|
||||
:with-delete (= action "edit"))}
|
||||
(when (= action "edit")
|
||||
[:> button* {:on-click on-delete-token
|
||||
:on-key-down handle-key-down-delete
|
||||
:class (stl/css :delete-btn)
|
||||
:type "button"
|
||||
:icon i/delete
|
||||
:variant "secondary"}
|
||||
(tr "labels.delete")])
|
||||
|
||||
[:> button* {:on-click on-cancel
|
||||
:on-key-down handle-key-down-cancel
|
||||
:type "button"
|
||||
:id "token-modal-cancel"
|
||||
:variant "secondary"}
|
||||
(tr "labels.cancel")]
|
||||
|
||||
[:> fc/form-submit* {:variant "primary"
|
||||
:on-submit on-submit}
|
||||
(tr "labels.save")]]]]))
|
||||
@@ -1,59 +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/typography.scss" as t;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
|
||||
.form-wrapper {
|
||||
width: $sz-384;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.token-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.form-modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: end;
|
||||
gap: var(--sp-m);
|
||||
padding-block-start: var(--sp-s);
|
||||
}
|
||||
|
||||
.with-delete {
|
||||
grid-template-columns: 1fr auto auto;
|
||||
}
|
||||
|
||||
.warning-name-change-notification-wrapper {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
justify-self: start;
|
||||
}
|
||||
@@ -31,18 +31,16 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.colorpicker :as colorpicker]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.typography :refer [font-selector*]]
|
||||
[app.main.ui.workspace.tokens.management.create.border-radius :as border-radius]
|
||||
[app.main.ui.workspace.tokens.management.create.color :as color]
|
||||
[app.main.ui.workspace.tokens.management.create.dimensions :as dimensions]
|
||||
[app.main.ui.workspace.tokens.management.create.font-family :as font-family]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token-color-bullet :refer [input-token-color-bullet*]]
|
||||
[app.main.ui.workspace.tokens.management.create.input-tokens-value :refer [input-token* token-value-hint*]]
|
||||
[app.main.ui.workspace.tokens.management.create.shadow :as shadow]
|
||||
[app.main.ui.workspace.tokens.management.create.text-case :as text-case]
|
||||
[app.main.ui.workspace.tokens.management.create.typography :as typography]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.functions :as uf]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -134,9 +132,8 @@
|
||||
[check-token-empty-value check-self-reference])
|
||||
|
||||
(defn- default-validate-token
|
||||
"Validates a token by confirming a list of `validator` predicates and
|
||||
resolving the token using `tokens` with StyleDictionary. Returns rx
|
||||
stream of either a valid resolved token or an errors map.
|
||||
"Validates a token by confirming a list of `validator` predicates and resolving the token using `tokens` with StyleDictionary.
|
||||
Returns rx stream of either a valid resolved token or an errors map.
|
||||
|
||||
Props:
|
||||
token-name, token-value, token-description: Values from the form inputs
|
||||
@@ -211,7 +208,7 @@
|
||||
|
||||
(defn- check-empty-shadow-token [token]
|
||||
(when (or (empty? (:value token))
|
||||
(some (fn [shadow] (not-every? #(contains? shadow %) [:offset-x :offset-y :blur :spread :color]))
|
||||
(some (fn [shadow] (not-every? #(contains? shadow %) [:offsetX :offsetY :blur :spread :color]))
|
||||
(:value token)))
|
||||
(wte/get-error-code :error.token/empty-input)))
|
||||
|
||||
@@ -234,26 +231,25 @@
|
||||
(default-validate-token))))
|
||||
|
||||
(defn- validate-shadow-token
|
||||
[{:keys [token-value] :as params}]
|
||||
[{:keys [token-value] :as props}]
|
||||
(cond
|
||||
;; Entering form without a value - show no error just resolve nil
|
||||
(nil? token-value) (rx/of nil)
|
||||
;; Validate refrence string
|
||||
(cto/shadow-composite-token-reference? token-value) (default-validate-token params)
|
||||
(cto/shadow-composite-token-reference? token-value) (default-validate-token props)
|
||||
;; Validate composite token
|
||||
:else
|
||||
(let [params (-> params
|
||||
(update :token-value (fn [value]
|
||||
(->> (or value [])
|
||||
(mapv (fn [shadow]
|
||||
(d/update-when shadow :inset #(cond
|
||||
(boolean? %) %
|
||||
(= "true" %) true
|
||||
:else false)))))))
|
||||
(assoc :validators [check-empty-shadow-token
|
||||
check-shadow-token-self-reference]))]
|
||||
|
||||
(default-validate-token params))))
|
||||
(-> props
|
||||
(update :token-value (fn [value]
|
||||
(->> (or value [])
|
||||
(mapv (fn [shadow]
|
||||
(d/update-when shadow :inset #(cond
|
||||
(boolean? %) %
|
||||
(= "true" %) true
|
||||
:else false)))))))
|
||||
(assoc :validators [check-empty-shadow-token
|
||||
check-shadow-token-self-reference])
|
||||
(default-validate-token))))
|
||||
|
||||
(defn- use-debonced-resolve-callback
|
||||
"Resolves a token values using `StyleDictionary`.
|
||||
@@ -739,11 +735,11 @@
|
||||
:selected (if reference-tab-active? "reference" "composite")
|
||||
:on-change on-toggle-tab
|
||||
:name "reference-composite-tab"}
|
||||
[:& radio-button {:icon i/layers
|
||||
[:& radio-button {:icon deprecated-icon/layers
|
||||
:value "composite"
|
||||
:title (tr "workspace.tokens.individual-tokens")
|
||||
:id "composite-opt"}]
|
||||
[:& radio-button {:icon i/tokens
|
||||
[:& radio-button {:icon deprecated-icon/tokens
|
||||
:value "reference"
|
||||
:title (tr "workspace.tokens.use-reference")
|
||||
:id "reference-opt"}]]]
|
||||
@@ -940,10 +936,10 @@
|
||||
|
||||
(def ^:private shadow-inputs
|
||||
#(d/ordered-map
|
||||
:offset-x
|
||||
:offsetX
|
||||
{:label (tr "workspace.tokens.shadow-x")
|
||||
:placeholder (tr "workspace.tokens.shadow-x")}
|
||||
:offset-y
|
||||
:offsetY
|
||||
{:label (tr "workspace.tokens.shadow-y")
|
||||
:placeholder (tr "workspace.tokens.shadow-y")}
|
||||
:blur
|
||||
@@ -978,11 +974,11 @@
|
||||
:name (str "inset-select-" shadow-idx)}
|
||||
[:& radio-button {:value "false"
|
||||
:title "false"
|
||||
:icon i/close
|
||||
:icon "❌"
|
||||
:id (str "inset-default-" shadow-idx)}]
|
||||
[:& radio-button {:value "true"
|
||||
:title "true"
|
||||
:icon i/tick
|
||||
:icon "✅"
|
||||
:id (str "inset-false-" shadow-idx)}]]]))
|
||||
|
||||
(mf/defc shadow-input*
|
||||
@@ -1441,16 +1437,13 @@
|
||||
(mf/spread-props props {:token-type token-type
|
||||
:validate-token default-validate-token
|
||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
||||
:token token})
|
||||
font-family-props (mf/spread-props props {:validate-token validate-font-family-token})
|
||||
typography-props (mf/spread-props props {:validate-token validate-typography-token})
|
||||
shadow-props (mf/spread-props props {:validate-token validate-shadow-token})]
|
||||
:token token})]
|
||||
|
||||
(case token-type
|
||||
:color [:> color/form* props]
|
||||
:typography [:> typography/form* typography-props]
|
||||
:shadow [:> shadow/form* shadow-props]
|
||||
:font-family [:> font-family/form* font-family-props]
|
||||
:typography [:> typography-form* props]
|
||||
:shadow [:> shadow-form* props]
|
||||
:font-family [:> font-family-form* props]
|
||||
:text-case [:> text-case/form* props]
|
||||
:text-decoration [:> text-decoration-form* props]
|
||||
:font-weight [:> font-weight-form* props]
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.form-color-input-token
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.color :as cl]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.utilities.swatch :refer [swatch*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.colorpicker :as colorpicker]
|
||||
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.core :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(defn- hex->color-obj
|
||||
[hex]
|
||||
(when-let [tc (tinycolor/valid-color hex)]
|
||||
(let [hex (tinycolor/->hex-string tc)
|
||||
alpha (tinycolor/alpha tc)
|
||||
[r g b] (cl/hex->rgb hex)
|
||||
[h s v] (cl/hex->hsv hex)]
|
||||
{:hex hex
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:alpha alpha})))
|
||||
|
||||
(mf/defc ramp*
|
||||
[{:keys [color on-change]}]
|
||||
(let [wrapper-node-ref (mf/use-ref nil)
|
||||
dragging-ref (mf/use-ref false)
|
||||
|
||||
on-start-drag
|
||||
(mf/use-fn #(mf/set-ref-val! dragging-ref true))
|
||||
|
||||
on-finish-drag
|
||||
(mf/use-fn #(mf/set-ref-val! dragging-ref false))
|
||||
|
||||
internal-color*
|
||||
(mf/use-state #(hex->color-obj color))
|
||||
|
||||
internal-color
|
||||
(deref internal-color*)
|
||||
|
||||
on-change'
|
||||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
(fn [{:keys [hex alpha] :as selector-color}]
|
||||
(let [dragging? (mf/ref-val dragging-ref)]
|
||||
(when-not (and dragging? hex)
|
||||
(reset! internal-color* selector-color)
|
||||
(on-change hex alpha)))))]
|
||||
(mf/use-effect
|
||||
(mf/deps color)
|
||||
(fn []
|
||||
;; Update internal color when user changes input value
|
||||
(when-let [color (tinycolor/valid-color color)]
|
||||
(when-not (= (tinycolor/->hex-string color) (:hex internal-color))
|
||||
(reset! internal-color* (hex->color-obj color))))))
|
||||
|
||||
(colorpicker/use-color-picker-css-variables! wrapper-node-ref internal-color)
|
||||
[:div {:ref wrapper-node-ref}
|
||||
[:> ramp-selector*
|
||||
{:color internal-color
|
||||
:on-start-drag on-start-drag
|
||||
:on-finish-drag on-finish-drag
|
||||
:on-change on-change'}]]))
|
||||
|
||||
(mf/defc form-color-input-token*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
resolved-input-name
|
||||
(mf/with-memo [input-name]
|
||||
(keyword (str "resolved-" (c/name input-name))))
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
resolved-value
|
||||
(get-in @form [:data resolved-input-name] "")
|
||||
|
||||
hex (if (tinycolor/valid-color resolved-value)
|
||||
(tinycolor/->hex-string (tinycolor/valid-color resolved-value))
|
||||
"#8f9da3")
|
||||
|
||||
alpha (if (tinycolor/valid-color resolved-value)
|
||||
(tinycolor/alpha (tinycolor/valid-color resolved-value))
|
||||
1)
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (:value token)]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
color-ramp-open* (mf/use-state false)
|
||||
color-ramp-open? (deref color-ramp-open*)
|
||||
|
||||
on-click-swatch
|
||||
(mf/use-fn
|
||||
(mf/deps color-ramp-open?)
|
||||
(fn []
|
||||
(let [open? (not color-ramp-open?)]
|
||||
(reset! color-ramp-open* open?))))
|
||||
|
||||
swatch
|
||||
(mf/html
|
||||
[:> swatch*
|
||||
{:background {:color hex :opacity alpha}
|
||||
:show-tooltip false
|
||||
:data-testid "token-form-color-bullet"
|
||||
:class (stl/css :slot-start)
|
||||
:on-click on-click-swatch}])
|
||||
|
||||
on-change-value
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name value)
|
||||
(fn [hex alpha]
|
||||
(let [;; StyleDictionary will always convert to hex/rgba, so we take the format from the value input field
|
||||
prev-input-color (some-> value
|
||||
(tinycolor/valid-color))
|
||||
;; If the input is a reference we will take the format from the computed value
|
||||
prev-computed-color (when-not prev-input-color
|
||||
(some-> value (tinycolor/valid-color)))
|
||||
prev-format (some-> (or prev-input-color prev-computed-color)
|
||||
(tinycolor/color-format))
|
||||
to-rgba? (and
|
||||
(< alpha 1)
|
||||
(or (= prev-format "hex") (not prev-format)))
|
||||
to-hex? (and (not prev-format) (= alpha 1))
|
||||
format (cond
|
||||
to-rgba? "rgba"
|
||||
to-hex? "hex"
|
||||
prev-format prev-format
|
||||
:else "hex")
|
||||
color-value (-> (tinycolor/valid-color hex)
|
||||
(tinycolor/set-alpha (or alpha 1))
|
||||
(tinycolor/->string format))]
|
||||
(when (not= value color-value)
|
||||
(fm/on-input-change form input-name color-value true)
|
||||
(rx/push! resolve-stream color-value)))))
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [raw-value (-> event dom/get-target dom/get-input-value)
|
||||
value (if (tinycolor/hex-without-hash-prefix? raw-value)
|
||||
(dm/str "#" raw-value)
|
||||
raw-value)]
|
||||
(fm/on-input-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
;; TODO: Review this value vs default-value
|
||||
:value (or value "")
|
||||
:hint-message (:message hint)
|
||||
:variant "comfortable"
|
||||
:slot-start swatch
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:errors input-name] {:message error})
|
||||
(swap! form assoc-in [:errors resolved-input-name] {:message error})
|
||||
(swap! form update :data dissoc resolved-input-name)
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :errors dissoc input-name resolved-input-name)
|
||||
(swap! form update :data assoc resolved-input-name value)
|
||||
(reset! hint* {:message message :type "hint"}))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:*
|
||||
[:> input* props]
|
||||
|
||||
(when color-ramp-open?
|
||||
[:> ramp* {:color value :on-change on-change-value}])]))
|
||||
@@ -0,0 +1,118 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.form-input-token
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.core :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(mf/defc form-input-token*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
resolved-input-name
|
||||
(mf/with-memo [input-name]
|
||||
(keyword (str "resolved-" (c/name input-name))))
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (:value token)]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(fm/on-input-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:default-value value
|
||||
:hint-message (:message hint)
|
||||
:variant "comfortable"
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:errors input-name] {:message error})
|
||||
(swap! form assoc-in [:errors resolved-input-name] {:message error})
|
||||
(swap! form update :data dissoc resolved-input-name)
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :errors dissoc input-name resolved-input-name)
|
||||
(swap! form update :data assoc resolved-input-name value)
|
||||
(reset! hint* {:message message :type "hint"}))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:> input* props]))
|
||||
@@ -1,314 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.input-token
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.workspace.tokens.format :as dwtf]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
(dissoc (:name prev-token))
|
||||
(update (:name token) #(ctob/make-token (merge % prev-token token))))]
|
||||
|
||||
(->> tokens
|
||||
(sd/resolve-tokens-interactive)
|
||||
(rx/mapcat
|
||||
(fn [resolved-tokens]
|
||||
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
|
||||
(if resolved-value
|
||||
(rx/of {:value resolved-value})
|
||||
(rx/of {:error (first errors)}))))))))
|
||||
|
||||
(mf/defc input-token*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if (contains? token :value)
|
||||
(rx/behavior-subject (:value token))
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(fm/on-input-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:default-value value
|
||||
:variant "comfortable"
|
||||
:hint-message (:message hint)
|
||||
:hint-type (:type hint)})
|
||||
props
|
||||
(if (and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
((:error/fn error) (:error/value error))))))
|
||||
(rx/subs! (fn [{:keys [error value]}]
|
||||
(let [touched? (get-in @form [:touched input-name])]
|
||||
(when touched?
|
||||
(if error
|
||||
(do
|
||||
(swap! form assoc-in [:extra-errors input-name] {:message error})
|
||||
(reset! hint* {:message error :type "error"}))
|
||||
(let [message (tr "workspace.tokens.resolved-value" value)]
|
||||
(swap! form update :extra-errors dissoc input-name)
|
||||
(reset! hint* {:message message :type "hint"}))))))))]
|
||||
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:> input* props]))
|
||||
|
||||
(defn- on-composite-input-token-change
|
||||
([form field value]
|
||||
(on-composite-input-token-change form field value false))
|
||||
([form field value trim?]
|
||||
(letfn [(clean-errors [errors]
|
||||
(-> errors
|
||||
(dissoc field)
|
||||
(not-empty)))]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
(mf/defc input-token-composite*
|
||||
[{:keys [name tokens token] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data :value input-name] "")
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get-in token [:value input-name])]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(on-composite-input-token-change form input-name value true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:default-value value
|
||||
:variant "comfortable"
|
||||
:hint-message (:message hint)
|
||||
:hint-type (:type hint)})
|
||||
props
|
||||
(if error
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)
|
||||
|
||||
props (if (and (not error) (= input-name :reference))
|
||||
(mf/spread-props props {:hint-formated true})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
(assoc error :message ((:error/fn error) (:error/value error)))))))
|
||||
|
||||
(rx/subs!
|
||||
(fn [{:keys [error value]}]
|
||||
(cond
|
||||
(and error (str/empty? (:error/value error)))
|
||||
(do
|
||||
(swap! form update-in [:errors :value] dissoc input-name)
|
||||
(swap! form update-in [:data :value] dissoc input-name)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(reset! hint* {}))
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
|
||||
input-value (get-in @form [:data :value input-name] "")]
|
||||
(swap! form update :errors dissoc :value)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(if (= input-value (str value))
|
||||
(reset! hint* {})
|
||||
(reset! hint* {:message message :type "hint"})))))
|
||||
(fn [cause]
|
||||
(js/console.log "MUU" cause))))]
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:> input* props]))
|
||||
|
||||
(defn- on-composite-indexed-input-token-change
|
||||
([form field index value composite-type]
|
||||
(on-composite-indexed-input-token-change form field index value composite-type false))
|
||||
([form field index value composite-type trim?]
|
||||
(letfn [(clean-errors [errors]
|
||||
(-> errors
|
||||
(dissoc field)
|
||||
(not-empty)))]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value composite-type index field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
(mf/defc input-token-indexed*
|
||||
[{:keys [name tokens token index composite-type] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value composite-type index input-name])
|
||||
|
||||
value-from-form
|
||||
(get-in @form [:data :value composite-type index input-name] "")
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token index input-name]
|
||||
(if-let [value (get-in token [:value composite-type index input-name])]
|
||||
(rx/behavior-subject value)
|
||||
(rx/subject)))
|
||||
|
||||
hint*
|
||||
(mf/use-state {})
|
||||
|
||||
hint
|
||||
(deref hint*)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps resolve-stream input-name index)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-input-value)]
|
||||
(on-composite-indexed-input-token-change form input-name index value composite-type true)
|
||||
(rx/push! resolve-stream value))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:on-change on-change
|
||||
:value value-from-form
|
||||
:variant "comfortable"
|
||||
:hint-message (:message hint)
|
||||
:hint-type (:type hint)})
|
||||
props
|
||||
(if error
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)
|
||||
|
||||
props
|
||||
(if (and (not error) (= input-name :reference))
|
||||
(mf/spread-props props {:hint-formated true})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name index composite-type]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
(assoc error :message ((:error/fn error) (:error/value error)))))))
|
||||
|
||||
(rx/subs!
|
||||
(fn [{:keys [error value]}]
|
||||
(cond
|
||||
(and error (str/empty? (:error/value error)))
|
||||
(do
|
||||
(swap! form update-in [:errors :value composite-type index] dissoc input-name)
|
||||
(swap! form update-in [:data :value composite-type index] dissoc input-name)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(reset! hint* {}))
|
||||
|
||||
(some? error)
|
||||
(let [error' (:message error)]
|
||||
(swap! form assoc-in [:extra-errors :value composite-type index input-name] {:message error'})
|
||||
(reset! hint* {:message error' :type "error"}))
|
||||
|
||||
:else
|
||||
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
|
||||
input-value (get-in @form [:data :value composite-type index input-name] "")]
|
||||
(swap! form update :errors dissoc :value)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(if (= input-value (str value))
|
||||
(reset! hint* {})
|
||||
(reset! hint* {:message message :type "hint"})))))))]
|
||||
(fn []
|
||||
(rx/dispose! subs))))
|
||||
|
||||
[:> input* props]))
|
||||
@@ -1,31 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.select-token
|
||||
(:require
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc select-composite*
|
||||
[{:keys [name index composite-type] :rest props}]
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
value
|
||||
(get-in @form [:data :value composite-type index input-name] false)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(mf/deps input-name)
|
||||
(fn [type]
|
||||
(let [is-inner? (= type "inner")]
|
||||
(swap! form assoc-in [:data :value composite-type index input-name] is-inner?))))
|
||||
|
||||
props (mf/spread-props props {:default-selected (if value "inner" "drop")
|
||||
:variant "ghost"
|
||||
:on-change on-change})]
|
||||
[:> select* props]))
|
||||
@@ -1,486 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.shadow
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as forms]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.tokens.management.create.color-input-token :refer [color-input-token-indexed*]]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token-composite* input-token-indexed*]]
|
||||
[app.main.ui.workspace.tokens.management.create.select-token :refer [select-composite*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private default-token-shadow
|
||||
{:offset-x "4"
|
||||
:offset-y "4"
|
||||
:blur "4"
|
||||
:spread "0"})
|
||||
|
||||
(defn get-subtoken
|
||||
[token index prop composite-type]
|
||||
(let [value (get-in token [:value composite-type index prop])]
|
||||
(d/without-nils
|
||||
{:type (if (= prop :color) :color :number)
|
||||
:value value})))
|
||||
|
||||
(mf/defc shadow-formset*
|
||||
[{:keys [index token tokens remove-shadow-block show-button composite-type] :as props}]
|
||||
(let [inset-token (get-subtoken token index :inset composite-type)
|
||||
inset-token (hooks/use-equal-memo inset-token)
|
||||
|
||||
color-token (get-subtoken token index :color composite-type)
|
||||
color-token (hooks/use-equal-memo color-token)
|
||||
|
||||
offset-x-token (get-subtoken token index :offset-x composite-type)
|
||||
offset-x-token (hooks/use-equal-memo offset-x-token)
|
||||
|
||||
offset-y-token (get-subtoken token index :offset-y composite-type)
|
||||
offset-y-token (hooks/use-equal-memo offset-y-token)
|
||||
|
||||
blur-token (get-subtoken token index :blur composite-type)
|
||||
blur-token (hooks/use-equal-memo blur-token)
|
||||
|
||||
spread-token (get-subtoken token index :spreadX composite-type)
|
||||
spread-token (hooks/use-equal-memo spread-token)
|
||||
|
||||
on-button-click
|
||||
(mf/use-fn
|
||||
(mf/deps index)
|
||||
(fn [event]
|
||||
(remove-shadow-block index event)))]
|
||||
|
||||
[:div {:class (stl/css :shadow-block)
|
||||
:data-testid (str "shadow-input-fields-" index)}
|
||||
[:div {:class (stl/css :select-wrapper)}
|
||||
[:> select-composite* {:options [{:id "drop" :label "drop shadow" :icon i/drop-shadow}
|
||||
{:id "inner" :label "inner shadow" :icon i/inner-shadow}]
|
||||
:aria-label (tr "workspace.tokens.shadow-inset")
|
||||
:token inset-token
|
||||
:tokens tokens
|
||||
:index index
|
||||
:composite-type composite-type
|
||||
:name :inset}]
|
||||
(when show-button
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:type "button"
|
||||
:aria-label (tr "workspace.tokens.shadow-remove-shadow")
|
||||
:on-click on-button-click
|
||||
:icon i/remove}])]
|
||||
[:div {:class (stl/css :inputs-wrapper)}
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> color-input-token-indexed*
|
||||
{:placeholder (tr "workspace.tokens.token-value-enter")
|
||||
:aria-label (tr "workspace.tokens.color")
|
||||
:name :color
|
||||
:token color-token
|
||||
:composite-type composite-type
|
||||
:index index
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-indexed*
|
||||
{:aria-label (tr "workspace.tokens.shadow-x")
|
||||
:icon i/character-x
|
||||
:placeholder (tr "workspace.tokens.shadow-x")
|
||||
:name :offset-x
|
||||
:token offset-x-token
|
||||
:index index
|
||||
:composite-type composite-type
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-indexed*
|
||||
{:aria-label (tr "workspace.tokens.shadow-y")
|
||||
:icon i/character-y
|
||||
:placeholder (tr "workspace.tokens.shadow-y")
|
||||
:name :offset-y
|
||||
:token offset-y-token
|
||||
:index index
|
||||
:composite-type composite-type
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-indexed*
|
||||
{:aria-label (tr "workspace.tokens.shadow-blur")
|
||||
:placeholder (tr "workspace.tokens.shadow-blur")
|
||||
:name :blur
|
||||
:slot-start (mf/html [:span {:class (stl/css :visible-label)}
|
||||
(str (tr "workspace.tokens.shadow-blur") ":")])
|
||||
:token blur-token
|
||||
:index index
|
||||
:composite-type composite-type
|
||||
:tokens tokens}]]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-indexed*
|
||||
{:aria-label (tr "workspace.tokens.shadow-spread")
|
||||
:placeholder (tr "workspace.tokens.shadow-spread")
|
||||
:name :spread
|
||||
:slot-start (mf/html [:span {:class (stl/css :visible-label)}
|
||||
(str (tr "workspace.tokens.shadow-spread") ":")])
|
||||
:token spread-token
|
||||
:composite-type composite-type
|
||||
:index index
|
||||
:tokens tokens}]]]]))
|
||||
|
||||
(mf/defc composite-form*
|
||||
[{:keys [token tokens remove-shadow-block composite-type] :as props}]
|
||||
(let [form
|
||||
(mf/use-ctx forms/context)
|
||||
|
||||
length
|
||||
(-> form deref :data :value composite-type count)]
|
||||
|
||||
(for [index (range length)]
|
||||
[:> shadow-formset* {:key index
|
||||
:index index
|
||||
:token token
|
||||
:tokens tokens
|
||||
:composite-type composite-type
|
||||
:remove-shadow-block remove-shadow-block
|
||||
:show-button (> length 1)}])))
|
||||
|
||||
(mf/defc reference-form*
|
||||
[{:keys [token tokens] :as props}]
|
||||
[:div {:class (stl/css :input-row-reference)}
|
||||
[:> input-token-composite*
|
||||
{:placeholder (tr "workspace.tokens.reference-composite-shadow")
|
||||
:aria-label (tr "labels.reference")
|
||||
:icon i/drop-shadow
|
||||
:name :reference
|
||||
:token token
|
||||
:tokens tokens}]])
|
||||
|
||||
(defn- make-schema
|
||||
[tokens-tree active-tab]
|
||||
(sm/schema
|
||||
[:and
|
||||
[:map
|
||||
[:name
|
||||
[:and
|
||||
[:string {:min 1 :max 255
|
||||
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
(sm/update-properties cto/token-name-ref assoc
|
||||
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||
|
||||
[:value
|
||||
[:map
|
||||
[:shadow {:optinal true}
|
||||
[:vector
|
||||
[:map
|
||||
[:offset-x {:optional true} [:maybe :string]]
|
||||
[:offset-y {:optional true} [:maybe :string]]
|
||||
[:blur {:optional true} [:maybe :string]]
|
||||
[:spread {:optional true} [:maybe :string]]
|
||||
[:color {:optional true} [:maybe :string]]
|
||||
[:color-result {:optional true} ::sm/any]
|
||||
[:inset {:optional true} [:maybe :boolean]]]]]
|
||||
(if (= active-tab :reference)
|
||||
[:reference {:optional false} ::sm/text]
|
||||
[:reference {:optional true} [:maybe :string]])]]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
[:fn {:error/field [:value :reference]
|
||||
:error/fn #(tr "workspace.tokens.self-reference")}
|
||||
(fn [{:keys [name value]}]
|
||||
(let [reference (get value :reference)]
|
||||
(if (and reference name)
|
||||
(not (cto/token-value-self-reference? name reference))
|
||||
true)))]
|
||||
|
||||
[:fn {:error/fn (fn [_] "Must be a valid shadow or reference")
|
||||
:error/field :value}
|
||||
(fn [{:keys [value]}]
|
||||
(let [reference (get value :reference)
|
||||
ref-valid? (and reference (not (str/blank? reference)))
|
||||
|
||||
shadows (get value :shadow)
|
||||
;; To be a valid shadow it must contain one on each valid values
|
||||
valid-composite-shadow?
|
||||
(and (seq shadows)
|
||||
(every?
|
||||
(fn [{:keys [offset-x offset-y blur spread color]}]
|
||||
(and (not (str/blank? offset-x))
|
||||
(not (str/blank? offset-y))
|
||||
(not (str/blank? blur))
|
||||
(not (str/blank? spread))
|
||||
(not (str/blank? color))))
|
||||
shadows))]
|
||||
|
||||
(or ref-valid? valid-composite-shadow?)))]]))
|
||||
|
||||
(mf/defc form*
|
||||
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
|
||||
|
||||
(let [active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
|
||||
composite-type :shadow
|
||||
|
||||
token
|
||||
(mf/with-memo [token]
|
||||
(or token
|
||||
(if-let [value (get token :value)]
|
||||
(cond
|
||||
(string? value)
|
||||
{:value {:reference value
|
||||
:shadow []}
|
||||
:type :shadow}
|
||||
|
||||
(vector? value)
|
||||
{:value {:reference nil
|
||||
:shadow value}
|
||||
:type :shadow})
|
||||
|
||||
{:type :shadow
|
||||
:value {:reference nil
|
||||
:shadow [default-token-shadow]}})))
|
||||
|
||||
token-type
|
||||
(get token :type)
|
||||
|
||||
token-properties
|
||||
(dwta/get-token-properties token)
|
||||
|
||||
token-title (str/lower (:title token-properties))
|
||||
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
(cond-> tokens
|
||||
(and (:name token) (:value token))
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [tokens-tree-in-selected-set active-tab]
|
||||
(make-schema tokens-tree-in-selected-set active-tab))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
(let [raw-value (:value token)
|
||||
|
||||
value
|
||||
(cond
|
||||
(string? raw-value)
|
||||
{:reference raw-value
|
||||
:shadow []}
|
||||
|
||||
(vector? raw-value)
|
||||
{:reference nil
|
||||
:shadow raw-value}
|
||||
|
||||
:else
|
||||
{:reference nil
|
||||
:shadow [default-token-shadow]})]
|
||||
|
||||
{:name (:name token "")
|
||||
:description (:description token "")
|
||||
:value value}))
|
||||
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps)
|
||||
(fn [new-tab]
|
||||
(let [new-tab (keyword new-tab)]
|
||||
(reset! active-tab* new-tab))))
|
||||
|
||||
on-cancel
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)))
|
||||
|
||||
on-delete-token
|
||||
(mf/use-fn
|
||||
(mf/deps selected-token-set-id token)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)
|
||||
(st/emit! (dwtl/delete-token selected-token-set-id (:id token)))))
|
||||
|
||||
handle-key-down-delete
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete-token)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-delete-token e))))
|
||||
|
||||
handle-key-down-cancel
|
||||
(mf/use-fn
|
||||
(mf/deps on-cancel)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-cancel e))))
|
||||
|
||||
on-add-shadow-block
|
||||
(mf/use-fn
|
||||
(mf/deps composite-type)
|
||||
(fn []
|
||||
(swap! form update-in [:data :value composite-type] conj default-token-shadow)))
|
||||
|
||||
remove-shadow-block
|
||||
(mf/use-fn
|
||||
(mf/deps composite-type)
|
||||
(fn [index event]
|
||||
(dom/prevent-default event)
|
||||
(swap! form update-in [:data :value composite-type] #(d/remove-at-index % index))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(mf/deps validate-token token tokens token-type active-tab composite-type)
|
||||
(fn [form _event]
|
||||
(let [name (get-in @form [:clean-data :name])
|
||||
description (get-in @form [:clean-data :description])
|
||||
value (get-in @form [:clean-data :value])]
|
||||
|
||||
(->> (validate-token {:token-value (if (= active-tab :reference)
|
||||
(:reference value)
|
||||
(composite-type value))
|
||||
:token-name name
|
||||
:token-description description
|
||||
:prev-token token
|
||||
:tokens tokens})
|
||||
(rx/subs!
|
||||
(fn [valid-token]
|
||||
(st/emit!
|
||||
(if is-create
|
||||
(dwtl/create-token (ctob/make-token {:name name
|
||||
:type token-type
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
|
||||
(dwtl/update-token (:id token)
|
||||
{:name name
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide))))))))]
|
||||
|
||||
[:> forms/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> forms/form-input* {:id "token-name"
|
||||
:name :name
|
||||
:label (tr "workspace.tokens.token-name")
|
||||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:auto-focus true}]
|
||||
|
||||
(when (and warning-name-change? (= action "edit"))
|
||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
||||
[:> context-notification*
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :title-bar)}
|
||||
[:div {:class (stl/css :title)} (tr "labels.shadow")]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:type "button"
|
||||
:aria-label (tr "workspace.tokens.shadow-add-shadow")
|
||||
:on-click on-add-shadow-block
|
||||
:icon i/add}]
|
||||
[:& radio-buttons {:class (stl/css :listing-options)
|
||||
:selected (d/name active-tab)
|
||||
:on-change on-toggle-tab
|
||||
:name "reference-composite-tab"}
|
||||
[:& radio-button {:icon i/layers
|
||||
:value "composite"
|
||||
:title (tr "workspace.tokens.individual-tokens")
|
||||
:id "composite-opt"}]
|
||||
[:& radio-button {:icon i/tokens
|
||||
:value "reference"
|
||||
:title (tr "workspace.tokens.use-reference")
|
||||
:id "reference-opt"}]]]
|
||||
|
||||
(if (= active-tab :composite)
|
||||
[:> composite-form* {:token token
|
||||
:tokens tokens
|
||||
:remove-shadow-block remove-shadow-block
|
||||
:composite-type composite-type}]
|
||||
|
||||
[:> reference-form* {:token token
|
||||
:tokens tokens}])
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> forms/form-input* {:id "token-description"
|
||||
:name :description
|
||||
:label (tr "workspace.tokens.token-description")
|
||||
:placeholder (tr "workspace.tokens.token-description")
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:is-optional true}]]
|
||||
|
||||
[:div {:class (stl/css-case :button-row true
|
||||
:with-delete (= action "edit"))}
|
||||
(when (= action "edit")
|
||||
[:> button* {:on-click on-delete-token
|
||||
:on-key-down handle-key-down-delete
|
||||
:class (stl/css :delete-btn)
|
||||
:type "button"
|
||||
:icon i/delete
|
||||
:variant "secondary"}
|
||||
(tr "labels.delete")])
|
||||
|
||||
[:> button* {:on-click on-cancel
|
||||
:on-key-down handle-key-down-cancel
|
||||
:type "button"
|
||||
:id "token-modal-cancel"
|
||||
:variant "secondary"}
|
||||
(tr "labels.cancel")]
|
||||
|
||||
[:> forms/form-submit* {:variant "primary"
|
||||
:on-submit on-submit}
|
||||
(tr "labels.save")]]]]))
|
||||
@@ -1,95 +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/typography.scss" as t;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
|
||||
.form-wrapper {
|
||||
width: $sz-384;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.token-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.inputs-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-m);
|
||||
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
||||
padding-inline-start: var(--sp-m);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.input-row-reference {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
||||
padding-inline-start: var(--sp-m);
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.title {
|
||||
@include t.use-typography("body-small");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: end;
|
||||
gap: var(--sp-m);
|
||||
padding-block-start: var(--sp-s);
|
||||
}
|
||||
|
||||
.with-delete {
|
||||
grid-template-columns: 1fr auto auto;
|
||||
}
|
||||
|
||||
.warning-name-change-notification-wrapper {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.visible-label {
|
||||
@include t.use-typography("headline-small");
|
||||
color: var(--color-foreground-secondary);
|
||||
line-height: $sz-32;
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as fc]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token*]]
|
||||
[app.main.ui.workspace.tokens.management.create.form-input-token :refer [form-input-token*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
[:value [::sm/text {:error/fn token-value-error-fn}]]
|
||||
|
||||
[:resolved-value ::sm/any]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
@@ -80,7 +82,7 @@
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
(mf/with-memo [tokens]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
@@ -168,9 +170,7 @@
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
(tr "workspace.tokens.create-token" token-type)]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> fc/form-input* {:id "token-name"
|
||||
@@ -187,7 +187,7 @@
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token*
|
||||
[:> form-input-token*
|
||||
{:placeholder (tr "workspace.tokens.text-case-value-enter")
|
||||
:label (tr "workspace.tokens.token-value")
|
||||
:name :value
|
||||
|
||||
@@ -1,429 +0,0 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.management.create.typography
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.token :as cto]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.forms :as forms]
|
||||
[app.main.ui.workspace.tokens.management.create.combobox-token-fonts :refer [font-picker-composite-combobox*]]
|
||||
[app.main.ui.workspace.tokens.management.create.input-token :refer [input-token-composite*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.forms :as fm]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc composite-form*
|
||||
[{:keys [token tokens] :as props}]
|
||||
(let [letter-spacing-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :letter-spacing
|
||||
:value (cto/join-font-family (get value :letter-spacing))}
|
||||
{:type :letter-spacing}))
|
||||
|
||||
font-family-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :font-family
|
||||
:value (get value :font-family)}
|
||||
{:type :font-family}))
|
||||
|
||||
font-size-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :font-size
|
||||
:value (get value :font-size)}
|
||||
{:type :font-size}))
|
||||
|
||||
font-weight-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :font-weight
|
||||
:value (get value :font-weight)}
|
||||
{:type :font-weight}))
|
||||
|
||||
;; TODO: Review this type
|
||||
line-height-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :number
|
||||
:value (get value :line-height)}
|
||||
{:type :number}))
|
||||
|
||||
text-case-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :text-case
|
||||
:value (get value :text-case)}
|
||||
{:type :text-case}))
|
||||
|
||||
text-decoration-sub-token
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get token :value)]
|
||||
{:type :text-decoration
|
||||
:value (get value :text-decoration)}
|
||||
{:type :text-decoration}))]
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> font-picker-composite-combobox*
|
||||
{:icon i/text-font-family
|
||||
:placeholder (tr "workspace.tokens.token-font-family-value-enter")
|
||||
:aria-label (tr "workspace.tokens.token-font-family-value")
|
||||
:name :font-family
|
||||
:token font-family-sub-token
|
||||
:tokens tokens}]]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:aria-label "Font Size"
|
||||
:icon i/text-font-size
|
||||
:placeholder (tr "workspace.tokens.font-size-value-enter")
|
||||
:name :font-size
|
||||
:token font-size-sub-token
|
||||
:tokens tokens}]]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:aria-label "Font Weight"
|
||||
:icon i/text-font-weight
|
||||
:placeholder (tr "workspace.tokens.font-weight-value-enter")
|
||||
:name :font-weight
|
||||
:token font-weight-sub-token
|
||||
:tokens tokens}]]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:aria-label "Line Height"
|
||||
:icon i/text-lineheight
|
||||
:placeholder (tr "workspace.tokens.line-height-value-enter")
|
||||
:name :line-height
|
||||
:token line-height-sub-token
|
||||
:tokens tokens}]]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:aria-label "Letter Spacing"
|
||||
:icon i/text-letterspacing
|
||||
:placeholder (tr "workspace.tokens.letter-spacing-value-enter-composite")
|
||||
:name :letter-spacing
|
||||
:token letter-spacing-sub-token
|
||||
:tokens tokens}]]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:aria-label "Text Case"
|
||||
:icon i/text-mixed
|
||||
:placeholder (tr "workspace.tokens.text-case-value-enter")
|
||||
:name :text-case
|
||||
:token text-case-sub-token
|
||||
:tokens tokens}]]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:aria-label "Text Decoration"
|
||||
:icon i/text-underlined
|
||||
:placeholder (tr "workspace.tokens.text-decoration-value-enter")
|
||||
:name :text-decoration
|
||||
:token text-decoration-sub-token
|
||||
:tokens tokens}]]]))
|
||||
|
||||
(mf/defc reference-form*
|
||||
[{:keys [token tokens] :as props}]
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> input-token-composite*
|
||||
{:placeholder (tr "workspace.tokens.reference-composite")
|
||||
:aria-label (tr "labels.reference")
|
||||
:icon i/text-typography
|
||||
:name :reference
|
||||
:token token
|
||||
:tokens tokens}]])
|
||||
|
||||
(defn- make-schema
|
||||
[tokens-tree active-tab]
|
||||
(sm/schema
|
||||
[:and
|
||||
[:map
|
||||
[:name
|
||||
[:and
|
||||
[:string {:min 1 :max 255
|
||||
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||
(sm/update-properties cto/token-name-ref assoc
|
||||
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||
#(not (cft/token-name-path-exists? % tokens-tree))]]]
|
||||
|
||||
[:value
|
||||
[:map
|
||||
[:font-family {:optional true} [:maybe :string]]
|
||||
[:font-size {:optional true} [:maybe :string]]
|
||||
[:font-weight {:optional true} [:maybe :string]]
|
||||
[:line-height {:optional true} [:maybe :string]]
|
||||
[:letter-spacing {:optional true} [:maybe :string]]
|
||||
[:text-case {:optional true} [:maybe :string]]
|
||||
[:text-decoration {:optional true} [:maybe :string]]
|
||||
(if (= active-tab :reference)
|
||||
[:reference {:optional false} ::sm/text]
|
||||
[:reference {:optional true} [:maybe :string]])]]
|
||||
|
||||
[:description {:optional true}
|
||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]
|
||||
|
||||
[:fn {:error/field [:value :reference]
|
||||
:error/fn #(tr "workspace.tokens.self-reference")}
|
||||
(fn [{:keys [name value]}]
|
||||
(let [reference (get value :reference)]
|
||||
(if (and reference name)
|
||||
(not (cto/token-value-self-reference? name reference))
|
||||
true)))]
|
||||
|
||||
[:fn {:error/field [:value :line-height]
|
||||
:error/fn #(tr "workspace.tokens.composite-line-height-needs-font-size")}
|
||||
(fn [{:keys [value]}]
|
||||
(let [line-heigh (get value :line-height)
|
||||
font-size (get value :font-size)]
|
||||
(if (and line-heigh (not font-size))
|
||||
false
|
||||
true)))]
|
||||
|
||||
;; This error does not shown on interface, it's just to avoid saving empty composite tokens
|
||||
;; We don't need to translate it.
|
||||
[:fn {:error/fn (fn [_] "At least one composite field must be set")
|
||||
:error/field :value}
|
||||
(fn [attrs]
|
||||
(let [result (reduce-kv (fn [_ _ v]
|
||||
(if (str/empty? v)
|
||||
false
|
||||
(reduced true)))
|
||||
false
|
||||
(get attrs :value))]
|
||||
result))]]))
|
||||
|
||||
(mf/defc form*
|
||||
[{:keys [token validate-token action is-create selected-token-set-id tokens-tree-in-selected-set] :as props}]
|
||||
|
||||
(let [token
|
||||
(mf/with-memo [token]
|
||||
(or token {:type :typography}))
|
||||
|
||||
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
|
||||
token-type
|
||||
(get token :type)
|
||||
|
||||
token-properties
|
||||
(dwta/get-token-properties token)
|
||||
|
||||
token-title (str/lower (:title token-properties))
|
||||
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
;; Ensure that the resolved value uses the currently editing token
|
||||
;; even if the name has been overriden by a token with the same name
|
||||
;; in another set below.
|
||||
(cond-> tokens
|
||||
(and (:name token) (:value token))
|
||||
(assoc (:name token) token)))
|
||||
|
||||
schema
|
||||
(mf/with-memo [tokens-tree-in-selected-set active-tab]
|
||||
(make-schema tokens-tree-in-selected-set active-tab))
|
||||
|
||||
initial
|
||||
(mf/with-memo [token]
|
||||
(let [value (:value token)
|
||||
processed-value
|
||||
(cond
|
||||
(string? value)
|
||||
{:reference value}
|
||||
|
||||
(map? value)
|
||||
(let [value (cond-> value
|
||||
(:font-family value)
|
||||
(update :font-family cto/join-font-family))]
|
||||
(select-keys value
|
||||
[:font-family
|
||||
:font-size
|
||||
:font-weight
|
||||
:line-height
|
||||
:letter-spacing
|
||||
:text-case
|
||||
:text-decoration]))
|
||||
:else
|
||||
{})]
|
||||
|
||||
{:name (:name token "")
|
||||
:value processed-value
|
||||
:description (:description token "")}))
|
||||
|
||||
form
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps)
|
||||
(fn [new-tab]
|
||||
(let [new-tab (keyword new-tab)]
|
||||
(reset! active-tab* new-tab))))
|
||||
|
||||
on-cancel
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)))
|
||||
|
||||
on-delete-token
|
||||
(mf/use-fn
|
||||
(mf/deps selected-token-set-id token)
|
||||
(fn [e]
|
||||
(dom/prevent-default e)
|
||||
(modal/hide!)
|
||||
(st/emit! (dwtl/delete-token selected-token-set-id (:id token)))))
|
||||
|
||||
handle-key-down-delete
|
||||
(mf/use-fn
|
||||
(mf/deps on-delete-token)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-delete-token e))))
|
||||
|
||||
handle-key-down-cancel
|
||||
(mf/use-fn
|
||||
(mf/deps on-cancel)
|
||||
(fn [e]
|
||||
(when (or (k/enter? e) (k/space? e))
|
||||
(on-cancel e))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(mf/deps validate-token token tokens token-type)
|
||||
(fn [form _event]
|
||||
(let [name (get-in @form [:clean-data :name])
|
||||
description (get-in @form [:clean-data :description])
|
||||
value (get-in @form [:clean-data :value])]
|
||||
|
||||
(->> (validate-token {:token-value (if (contains? value :reference)
|
||||
(get value :reference)
|
||||
value)
|
||||
:token-name name
|
||||
:token-description description
|
||||
:prev-token token
|
||||
:tokens tokens})
|
||||
(rx/subs!
|
||||
(fn [valid-token]
|
||||
(st/emit!
|
||||
(if is-create
|
||||
(dwtl/create-token (ctob/make-token {:name name
|
||||
:type token-type
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
|
||||
(dwtl/update-token (:id token)
|
||||
{:name name
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide))))))))]
|
||||
|
||||
[:> forms/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :token-rows)}
|
||||
|
||||
[:> heading* {:level 2 :typography "headline-medium" :class (stl/css :form-modal-title)}
|
||||
(if (= action "edit")
|
||||
(tr "workspace.tokens.edit-token" token-type)
|
||||
(tr "workspace.tokens.create-token" token-type))]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> forms/form-input* {:id "token-name"
|
||||
:name :name
|
||||
:label (tr "workspace.tokens.token-name")
|
||||
:placeholder (tr "workspace.tokens.enter-token-name" token-title)
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:auto-focus true}]
|
||||
|
||||
(when (and warning-name-change? (= action "edit"))
|
||||
[:div {:class (stl/css :warning-name-change-notification-wrapper)}
|
||||
[:> context-notification*
|
||||
{:level :warning :appearance :ghost} (tr "workspace.tokens.warning-name-change")]])]
|
||||
|
||||
[:div {:class (stl/css :title-bar)}
|
||||
[:div {:class (stl/css :title)} (tr "labels.typography")]
|
||||
[:& radio-buttons {:class (stl/css :listing-options)
|
||||
:selected (d/name active-tab)
|
||||
:on-change on-toggle-tab
|
||||
:name "reference-composite-tab"}
|
||||
[:& radio-button {:icon i/layers
|
||||
:value "composite"
|
||||
:title (tr "workspace.tokens.individual-tokens")
|
||||
:id "composite-opt"}]
|
||||
[:& radio-button {:icon i/tokens
|
||||
:value "reference"
|
||||
:title (tr "workspace.tokens.use-reference")
|
||||
:id "reference-opt"}]]]
|
||||
[:div {:class (stl/css :inputs-wrapper)}
|
||||
(if (= active-tab :composite)
|
||||
[:> composite-form* {:token token
|
||||
:tokens tokens}]
|
||||
|
||||
[:> reference-form* {:token token
|
||||
:tokens tokens}])]
|
||||
|
||||
[:div {:class (stl/css :input-row)}
|
||||
[:> forms/form-input* {:id "token-description"
|
||||
:name :description
|
||||
:label (tr "workspace.tokens.token-description")
|
||||
:placeholder (tr "workspace.tokens.token-description")
|
||||
:max-length max-input-length
|
||||
:variant "comfortable"
|
||||
:is-optional true}]]
|
||||
|
||||
[:div {:class (stl/css-case :button-row true
|
||||
:with-delete (= action "edit"))}
|
||||
(when (= action "edit")
|
||||
[:> button* {:on-click on-delete-token
|
||||
:on-key-down handle-key-down-delete
|
||||
:class (stl/css :delete-btn)
|
||||
:type "button"
|
||||
:icon i/delete
|
||||
:variant "secondary"}
|
||||
(tr "labels.delete")])
|
||||
|
||||
[:> button* {:on-click on-cancel
|
||||
:on-key-down handle-key-down-cancel
|
||||
:type "button"
|
||||
:id "token-modal-cancel"
|
||||
:variant "secondary"}
|
||||
(tr "labels.cancel")]
|
||||
|
||||
[:> forms/form-submit* {:variant "primary"
|
||||
:on-submit on-submit}
|
||||
(tr "labels.save")]]]]))
|
||||
@@ -1,74 +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/typography.scss" as t;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
|
||||
.form-wrapper {
|
||||
width: $sz-384;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.token-rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
}
|
||||
|
||||
.inputs-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-m);
|
||||
border-inline-start: $b-1 solid var(--color-accent-primary-muted);
|
||||
padding-inline-start: var(--sp-m);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include t.use-typography("body-small");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
color: var(--color-foreground-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: end;
|
||||
gap: var(--sp-m);
|
||||
padding-block-start: var(--sp-s);
|
||||
}
|
||||
|
||||
.with-delete {
|
||||
grid-template-columns: 1fr auto auto;
|
||||
}
|
||||
|
||||
.warning-name-change-notification-wrapper {
|
||||
margin-block-start: var(--sp-l);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
justify-self: start;
|
||||
}
|
||||
@@ -43,7 +43,6 @@
|
||||
:stroke-width "stroke-size"
|
||||
:dimensions "expand"
|
||||
:sizing "expand"
|
||||
:shadow "drop-shadow"
|
||||
"add"))
|
||||
|
||||
(mf/defc token-group*
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.tokens.sets.lists :as wts]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
@@ -93,11 +94,11 @@
|
||||
:name name}
|
||||
[:& radio-button {:id :on
|
||||
:value :on
|
||||
:icon i/tick
|
||||
:icon deprecated-icon/tick
|
||||
:label ""}]
|
||||
[:& radio-button {:id :off
|
||||
:value :off
|
||||
:icon i/close
|
||||
:icon deprecated-icon/close
|
||||
:label ""}]]))
|
||||
|
||||
(mf/defc themes-overview
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
[{:keys [x y stroke action-type arrow-dir zoom] :as props}]
|
||||
(let [icon-pdata (case action-type
|
||||
:navigate (case arrow-dir
|
||||
:right "M -6.5 0 L 5.5 0 M 6.715 0.715 L -0.5 -6.5 M 6.715 -0.715 L -0.365 6.635"
|
||||
:left "M 6.5 0 l -12 0 m -0.715 0.715 l 6.5 -6.9 m -6 6 l 6 6.35"
|
||||
:right "M -6.5 0 l 12 0 l -6 -6 m 6 6 l -6 6"
|
||||
:left "M 6.5 0 l -12 0 l 6 -6 m -6 6 l 6 6"
|
||||
nil)
|
||||
|
||||
:open-overlay "M-5 -5 h7 v7 h-7 z M2 -2 h3.5 v7 h-7 v-2.5"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
@@ -276,6 +277,7 @@
|
||||
pos (gpt/point x (- y (/ 35 zoom)))
|
||||
|
||||
frame-id (:id frame)
|
||||
flow-id (:id flow)
|
||||
flow-name (:name flow)
|
||||
|
||||
on-pointer-down
|
||||
@@ -289,6 +291,11 @@
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dcm/go-to-viewer params))))))
|
||||
|
||||
on-double-click
|
||||
(mf/use-fn
|
||||
(mf/deps flow-id)
|
||||
#(st/emit! (dwi/start-rename-flow flow-id)))
|
||||
|
||||
on-pointer-enter
|
||||
(mf/use-fn
|
||||
(mf/deps frame-id on-frame-enter)
|
||||
@@ -312,6 +319,7 @@
|
||||
[:div {:class (stl/css-case :frame-flow-badge-content true
|
||||
:selected is-selected)
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-double-click on-double-click
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave}
|
||||
[:> icon* {:icon-id i/play
|
||||
|
||||
@@ -151,8 +151,8 @@
|
||||
;; export interface Shadow {
|
||||
;; id?: string;
|
||||
;; style?: 'drop-shadow' | 'inner-shadow';
|
||||
;; offset-x?: number;
|
||||
;; offset-y?: number;
|
||||
;; offsetX?: number;
|
||||
;; offsetY?: number;
|
||||
;; blur?: number;
|
||||
;; spread?: number;
|
||||
;; hidden?: boolean;
|
||||
@@ -164,8 +164,8 @@
|
||||
(obj/without-empty
|
||||
#js {:id (-> id format-id)
|
||||
:style (-> style format-key)
|
||||
:offset-x offset-x
|
||||
:offset-y offset-y
|
||||
:offsetX offset-x
|
||||
:offsetY offset-y
|
||||
:blur blur
|
||||
:spread spread
|
||||
:hidden hidden
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
(ns app.plugins.parser
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.json :as json]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]))
|
||||
@@ -147,7 +145,7 @@
|
||||
;; export interface Shadow {
|
||||
;; id?: string;
|
||||
;; style?: 'drop-shadow' | 'inner-shadow';
|
||||
;; offset--y?: number;
|
||||
;; offsetX?: number;
|
||||
;; offsetY?: number;
|
||||
;; blur?: number;
|
||||
;; spread?: number;
|
||||
@@ -160,8 +158,8 @@
|
||||
(d/without-nils
|
||||
{:id (-> (obj/get shadow "id") parse-id)
|
||||
:style (-> (obj/get shadow "style") parse-keyword)
|
||||
:offset-x (obj/get shadow "offset-x")
|
||||
:offset-y (obj/get shadow "offset-y")
|
||||
:offset-x (obj/get shadow "offsetX")
|
||||
:offset-y (obj/get shadow "offsetY")
|
||||
:blur (obj/get shadow "blur")
|
||||
:spread (obj/get shadow "spread")
|
||||
:hidden (obj/get shadow "hidden")
|
||||
@@ -516,8 +514,3 @@
|
||||
(case axis
|
||||
"horizontal" :y
|
||||
"vertical" :x))
|
||||
|
||||
(defn parse-commands
|
||||
[commands]
|
||||
(-> (json/->clj commands)
|
||||
(path/decode-segments)))
|
||||
|
||||
@@ -1033,8 +1033,8 @@
|
||||
(fn []
|
||||
(let [shape (u/locate-shape file-id page-id id)]
|
||||
(cond
|
||||
(and (not (cfh/path-shape? shape)) (not (cfh/bool-shape? shape)))
|
||||
(u/display-not-valid :toD (:type shape))
|
||||
(not (cfh/path-shape? shape))
|
||||
(u/display-not-valid :makeMask (:type shape))
|
||||
|
||||
:else
|
||||
(.toString (:content shape)))))
|
||||
@@ -1487,37 +1487,13 @@
|
||||
|
||||
(cond-> (or (cfh/path-shape? data) (cfh/bool-shape? data))
|
||||
(crc/add-properties!
|
||||
{:name "commands"
|
||||
:get #(-> % u/proxy->shape :content format/format-path-content)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [segments (parser/parse-commands value)]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :content "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not (sm/validate path/schema:segments segments))
|
||||
(u/display-not-valid :content segments)
|
||||
|
||||
:else
|
||||
(let [selrect (path/calc-selrect segments)
|
||||
content (path/from-plain segments)
|
||||
points (grc/rect->points selrect)]
|
||||
(st/emit! (dwsh/update-shapes
|
||||
[id]
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(assoc :content content)
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points points)))))))))}
|
||||
{:name "d"
|
||||
{:name "content"
|
||||
:get #(-> % u/proxy->shape :content str)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [segments
|
||||
(if (string? value)
|
||||
(svg.path/parse value)
|
||||
value)]
|
||||
(let [segments (if (string? value)
|
||||
(svg.path/parse value)
|
||||
value)]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :content "Plugin doesn't have 'content:write' permission")
|
||||
@@ -1537,7 +1513,4 @@
|
||||
(-> shape
|
||||
(assoc :content content)
|
||||
(assoc :selrect selrect)
|
||||
(assoc :points points)))))))))}
|
||||
{:name "content"
|
||||
:get #(.-d %)
|
||||
:set (fn [self value] (set! (.-d self) value))}))))))
|
||||
(assoc :points points)))))))))}))))))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
(ns app.util.code-highlight
|
||||
(:require
|
||||
["@penpot/hljs" :as hljs]
|
||||
["highlight.js$default" :as hljs]
|
||||
[app.util.dom :as dom]))
|
||||
|
||||
(defn highlight!
|
||||
|
||||
@@ -48,11 +48,7 @@
|
||||
(let [props (m/properties schema)
|
||||
tprops (m/type-properties schema)
|
||||
field (or (first in)
|
||||
(:error/field props))
|
||||
|
||||
field (if (vector? field)
|
||||
field
|
||||
[field])]
|
||||
(:error/field props))]
|
||||
|
||||
(if (contains? acc field)
|
||||
acc
|
||||
@@ -62,30 +58,30 @@
|
||||
|
||||
(or (= type :malli.core/missing-key)
|
||||
(nil? value))
|
||||
(assoc-in acc field {:message (tr "errors.field-missing")})
|
||||
(assoc acc field {:message (tr "errors.field-missing")})
|
||||
|
||||
;; --- CHECK on schema props
|
||||
(contains? props :error/fn)
|
||||
(assoc-in acc field (handle-error-fn props problem))
|
||||
(assoc acc field (handle-error-fn props problem))
|
||||
|
||||
(contains? props :error/message)
|
||||
(assoc-in acc field (handle-error-message props))
|
||||
(assoc acc field (handle-error-message props))
|
||||
|
||||
(contains? props :error/code)
|
||||
(assoc-in acc field (handle-error-code props))
|
||||
(assoc acc field (handle-error-code props))
|
||||
|
||||
;; --- CHECK on type props
|
||||
(contains? tprops :error/fn)
|
||||
(assoc-in acc field (handle-error-fn tprops problem))
|
||||
(assoc acc field (handle-error-fn tprops problem))
|
||||
|
||||
(contains? tprops :error/message)
|
||||
(assoc-in acc field (handle-error-message tprops))
|
||||
(assoc acc field (handle-error-message tprops))
|
||||
|
||||
(contains? tprops :error/code)
|
||||
(assoc-in acc field (handle-error-code tprops))
|
||||
(assoc acc field (handle-error-code tprops))
|
||||
|
||||
:else
|
||||
(assoc-in acc field {:message (tr "errors.invalid-data")})))))
|
||||
(assoc acc field {:message (tr "errors.invalid-data")})))))
|
||||
|
||||
(defn- use-rerender-fn
|
||||
[]
|
||||
@@ -118,25 +114,20 @@
|
||||
[f {:keys [schema validators]}]
|
||||
(fn [& args]
|
||||
(let [state (apply f args)
|
||||
cleaned (sm/decode schema (:data state) sm/json-transformer)
|
||||
cleaned (sm/decode schema (:data state) sm/string-transformer)
|
||||
valid? (sm/validate schema cleaned)
|
||||
|
||||
errors
|
||||
(when-not valid?
|
||||
(collect-schema-errors schema validators state))
|
||||
|
||||
extra-errors
|
||||
(not-empty (:extra-errors state))]
|
||||
errors (when-not valid?
|
||||
(collect-schema-errors schema validators state))]
|
||||
|
||||
(assoc state
|
||||
:errors errors
|
||||
:clean-data (when valid? cleaned)
|
||||
:valid (and (not errors)
|
||||
(not extra-errors)
|
||||
valid?)))))
|
||||
:valid (and (not errors) valid?)))))
|
||||
|
||||
(defn- create-form-mutator
|
||||
[internal-state rerender-fn wrap-update-fn initial opts]
|
||||
(mf/set-ref-val! internal-state initial)
|
||||
|
||||
(reify
|
||||
IDeref
|
||||
(-deref [_]
|
||||
@@ -171,7 +162,7 @@
|
||||
(rerender-fn)))))
|
||||
|
||||
(defn use-form
|
||||
[& {:keys [initial schema validators] :as opts}]
|
||||
[& {:keys [initial] :as opts}]
|
||||
(let [rerender-fn (use-rerender-fn)
|
||||
|
||||
initial
|
||||
@@ -184,15 +175,8 @@
|
||||
(mf/use-ref nil)
|
||||
|
||||
form-mutator
|
||||
(mf/with-memo [initial schema validators]
|
||||
(let [mutator (create-form-mutator internal-state rerender-fn wrap-update-schema-fn
|
||||
initial
|
||||
(select-keys opts [:schema :validators]))]
|
||||
(swap! mutator identity)
|
||||
mutator))]
|
||||
|
||||
(mf/with-effect [initial]
|
||||
(mf/set-ref-val! internal-state initial))
|
||||
(mf/with-memo [initial]
|
||||
(create-form-mutator internal-state rerender-fn wrap-update-schema-fn initial opts))]
|
||||
|
||||
;; Initialize internal state once
|
||||
(mf/with-layout-effect []
|
||||
@@ -207,16 +191,11 @@
|
||||
([form field value]
|
||||
(on-input-change form field value false))
|
||||
([form field value trim?]
|
||||
(letfn [(clean-errors [errors]
|
||||
(-> errors
|
||||
(dissoc field)
|
||||
(not-empty)))]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:touched field] true)
|
||||
(assoc-in [:data field] (if trim? (str/trim value) value))
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:touched field] true)
|
||||
(assoc-in [:data field] (if trim? (str/trim value) value))
|
||||
(update :errors dissoc field))))))
|
||||
|
||||
(defn update-input-value!
|
||||
[form field value]
|
||||
|
||||
@@ -474,8 +474,8 @@
|
||||
(t/async
|
||||
done
|
||||
(let [shadow-token {:name "shadow.sm"
|
||||
:value [{:offset-x 10
|
||||
:offset-y 10
|
||||
:value [{:offsetX 10
|
||||
:offsetY 10
|
||||
:blur 10
|
||||
:spread 10
|
||||
:color "rgba(0,0,0,0.5)"
|
||||
|
||||
@@ -1853,7 +1853,7 @@ msgstr "Select a shape, board or group to inspect their properties and code"
|
||||
|
||||
#: src/app/main/ui/inspect/right_sidebar.cljs:166
|
||||
msgid "inspect.layer-info"
|
||||
msgstr "Layer info"
|
||||
msgstr "Select inspect tab"
|
||||
|
||||
#: src/app/main/ui/inspect/right_sidebar.cljs:137
|
||||
msgid "inspect.multiple-selected"
|
||||
@@ -7753,10 +7753,6 @@ msgstr "Invalid token value: only none, underline and strike-through are accepte
|
||||
msgid "workspace.tokens.invalid-token-value-typography"
|
||||
msgstr "Invalid value: must reference a composite typography token."
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs
|
||||
msgid "workspace.tokens.invalid-token-value-shadow"
|
||||
msgstr "Invalid value: must reference a composite shadow token."
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:61, src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/errors.cljs:77
|
||||
msgid "workspace.tokens.invalid-value"
|
||||
msgstr "Invalid token value: %s"
|
||||
@@ -7874,10 +7870,6 @@ msgstr "Reference is not valid or is not in any active set"
|
||||
msgid "workspace.tokens.reference-composite"
|
||||
msgstr "Enter a token typography alias"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
|
||||
msgid "workspace.tokens.reference-composite-shadow"
|
||||
msgstr "Enter a token shadow alias"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
|
||||
#, unused
|
||||
msgid "workspace.tokens.reference-error"
|
||||
|
||||
@@ -7674,10 +7674,6 @@ msgstr "Tipo de sombra no válida: solo se aceptan 'innerShadow' o 'dropShadow'"
|
||||
msgid "workspace.tokens.invalid-token-value-typography"
|
||||
msgstr "Valor no válido: debe hacer referencia a un token tipográfico compuesto."
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs
|
||||
msgid "workspace.tokens.invalid-token-value-shadow"
|
||||
msgstr "Valor no válido: debe hacer referencia a un token de sombra compuesto."
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:61, src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/errors.cljs:77
|
||||
msgid "workspace.tokens.invalid-value"
|
||||
msgstr "Valor de token no válido: %s"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user