Compare commits

...

107 Commits

Author SHA1 Message Date
Andrés Moya 5fa4368d70
🔧 Refactor integration test to be cleaner (#8044) 2026-01-09 12:52:50 +01:00
Elena Torró b8efd2518d
🐛 Fix invite members UI modal (#8032) 2026-01-09 11:12:15 +01:00
Andrés Moya 7b2271ec38
🐛 Fix remapping of tokens with the same name and update tests (#8043) 2026-01-09 10:53:19 +01:00
Xaviju 2240d93069
Save unfolded tokens path (#7949) 2026-01-09 09:56:18 +01:00
Edgars Andersons 3f4506284b
🌐 Add translations for: Latvian
Currently translated at 91.3% (1869 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-08 18:06:51 +01:00
Valentina Chapellu af1dfd91aa
🌐 Add translations for: Italian
Currently translated at 97.0% (1984 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-08 18:06:51 +01:00
Mikel Larreategi 24feebd73b
🌐 Add translations for: Basque
Currently translated at 56.4% (1155 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2026-01-08 18:06:51 +01:00
Aryiu 33e5a9a538
🌐 Add translations for: Catalan
Currently translated at 52.2% (1068 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2026-01-08 18:06:50 +01:00
Linerly 9c69b07a62
🌐 Add translations for: Indonesian
Currently translated at 82.9% (1697 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2026-01-08 18:06:50 +01:00
Црнобог 56f5be4f37
🌐 Add translations for: Serbian
Currently translated at 67.0% (1371 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sr/
2026-01-08 18:06:50 +01:00
ascarida 8a70204d41
🌐 Add translations for: Galician
Currently translated at 18.0% (370 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/
2026-01-08 18:06:50 +01:00
Henrik Allberg 57a27f7e7f
🌐 Add translations for: Swedish
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-01-08 18:06:50 +01:00
Eranot 3b0b2a78d6
🌐 Add translations for: Portuguese (Brazil)
Currently translated at 68.1% (1394 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2026-01-08 18:06:50 +01:00
Alejandro Alonso 10bf4610df
🌐 Add translations for: Hausa
Currently translated at 60.6% (1241 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ha/
2026-01-08 18:06:50 +01:00
Andy Li 77e8414aea
🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 78.1% (1599 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-01-08 18:06:50 +01:00
bingling_sama 20ecf3b066
🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 88.2% (1804 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2026-01-08 18:06:50 +01:00
Amerey.eu 49b1032973
🌐 Add translations for: Czech
Currently translated at 77.9% (1594 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2026-01-08 18:06:49 +01:00
Radek Sawicki 5ba7dd8c56
🌐 Add translations for: Polish
Currently translated at 55.2% (1130 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2026-01-08 18:06:49 +01:00
Ingrid Pigueron 38b5125186
🌐 Add translations for: French
Currently translated at 94.5% (1934 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-01-08 18:06:49 +01:00
Vint Prox 6677ae83d4
🌐 Add translations for: Russian
Currently translated at 77.3% (1582 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-01-08 18:06:49 +01:00
Marius 0737c055f0
🌐 Add translations for: German
Currently translated at 93.2% (1906 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-01-08 18:06:49 +01:00
Dário 4b88748fe3
🌐 Add translations for: Portuguese (Portugal)
Currently translated at 76.8% (1571 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/
2026-01-08 18:06:49 +01:00
Denys Kisil 92107e5b1e
🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 88.8% (1818 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2026-01-08 18:06:49 +01:00
Shuaib Zahda ebc0e3a23c
🌐 Add translations for: Arabic
Currently translated at 55.0% (1126 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2026-01-08 18:06:49 +01:00
VKing9 ebe4f2da50
🌐 Add translations for: Hindi
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-08 18:06:48 +01:00
Vincas Dundzys a07c1d6eaa
🌐 Add translations for: Lithuanian
Currently translated at 5.7% (118 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2026-01-08 18:06:48 +01:00
Ahmad HosseinBor 613bfda955
🌐 Add translations for: Persian
Currently translated at 38.2% (782 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2026-01-08 18:06:48 +01:00
AlexTECPlayz f7ef6618e5
🌐 Add translations for: Romanian
Currently translated at 94.8% (1940 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2026-01-08 18:06:48 +01:00
Sebastiaan Pasma fe334d9cbe
🌐 Add translations for: Dutch
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-08 18:06:48 +01:00
Revenant 268b883c73
🌐 Add translations for: Malay
Currently translated at 32.8% (672 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ms/
2026-01-08 18:06:48 +01:00
Zvonimir Juranko f6a4effa29
🌐 Add translations for: Croatian
Currently translated at 78.1% (1599 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/
2026-01-08 18:06:48 +01:00
Yessenia Villarte Vaca ced848077e
🌐 Add translations for: Spanish (Latin America)
Currently translated at 6.4% (131 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es_419/
2026-01-08 18:06:48 +01:00
Alexis Morin 7d9d318539
🌐 Add translations for: French (Canada)
Currently translated at 12.5% (257 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 18:06:48 +01:00
Oğuz Ersen 9781fceadb
🌐 Add translations for: Turkish
Currently translated at 97.1% (1986 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-08 18:06:48 +01:00
Yaron Shahrabani 3178bd9a27
🌐 Add translations for: Hebrew
Currently translated at 97.0% (1984 of 2045 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 18:06:47 +01:00
Andrey Antukh e5d677f449 🌐 Validate and rehash translation files 2026-01-08 18:05:51 +01:00
Andrey Antukh 6bf928893c
Merge pull request #8000 from penpot/luis-radio-buttons-ds
♻️ Replace some components with DS ones
2026-01-08 18:04:20 +01:00
Andrés Moya 53dd90aa24
🔥 Remove unused css (#8039) 2026-01-08 16:37:27 +01:00
Andrey Antukh 9fd0f6a8f3 📎 Fix integration tests 2026-01-08 16:02:52 +01:00
Andrey Antukh 638c3356d3 📎 Use correct casing on translation strings 2026-01-08 14:58:17 +01:00
Luis de Dios 6879f54e5d ♻️ Replace some components with DS ones 2026-01-08 14:52:25 +01:00
Andrey Antukh a71baa5a78 🌐 Rehash and validate translation files 2026-01-08 14:46:18 +01:00
Andrey Antukh 8e4a89bd1c Merge branch 'staging' into develop 2026-01-08 14:43:43 +01:00
Andrey Antukh 90efb665b5 Add several additional renames for make translation string consistent 2026-01-08 14:37:58 +01:00
Pablo Alba 47ee490158 🐛 Fix typos on download modal 2026-01-08 14:37:58 +01:00
Andrey Antukh f0f89599bc 🌐 Backport translations from develop 2026-01-08 14:08:02 +01:00
Hosted Weblate 7aad9da285
🌐 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2026-01-08 14:04:56 +01:00
Alexis Morin ab57a4ae52
🌐 Add translations for: French (Canada)
Currently translated at 12.9% (259 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin 266ee29bb9
🌐 Add translations for: French (Canada)
Currently translated at 9.2% (184 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin 69ca86bb6c
🌐 Add translations for: French (Canada)
Currently translated at 7.3% (147 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Alexis Morin ee14a845fc
🌐 Add translations for: French (Canada)
Currently translated at 3.1% (62 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:48 +01:00
Yaron Shahrabani 73639f5d16
🌐 Add translations for: Hebrew
Currently translated at 99.7% (1992 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 14:04:48 +01:00
Yaron Shahrabani 9bd106b2bc
🌐 Add translations for: Hebrew
Currently translated at 99.4% (1986 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-01-08 14:04:47 +01:00
Alexis Morin 59c75afc7b
🌐 Add translations for: French (Canada)
Currently translated at 1.0% (21 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:47 +01:00
Nicola Bortoletto bbc81586e3
🌐 Add translations for: Italian
Currently translated at 99.7% (1992 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-01-08 14:04:47 +01:00
Anton Palmqvist c9c30eab75
🌐 Add translations for: Swedish
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-01-08 14:04:47 +01:00
Alexis Morin 86ba9280db
🌐 Add translations for: French (Canada)
Currently translated at 0.3% (6 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-01-08 14:04:47 +01:00
Vin 5800cc4bb2
🌐 Add translations for: Russian
Currently translated at 79.2% (1583 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-01-08 14:04:47 +01:00
andy aa29a34c4c
🌐 Added translation for: French (Canada) 2026-01-08 14:04:47 +01:00
Edgars Andersons 3276129cc7
🌐 Add translations for: Latvian
Currently translated at 93.9% (1876 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lv/
2026-01-08 14:04:47 +01:00
VKing9 67a96de475
🌐 Add translations for: Hindi
Currently translated at 100.0% (1997 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-01-08 14:04:47 +01:00
Stephan Paternotte 48785b4846
🌐 Add translations for: Dutch
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-01-08 14:04:46 +01:00
Oğuz Ersen 3f0573f95d
🌐 Add translations for: Turkish
Currently translated at 99.8% (1994 of 1997 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-01-08 14:04:46 +01:00
Andrey Antukh d94a2a8881 Merge branch 'staging-render' into develop 2026-01-08 13:59:01 +01:00
Andrey Antukh 1c237a0968 Merge branch 'staging' into staging-render 2026-01-08 13:58:48 +01:00
Andrey Antukh b0dc7d6ffb 🔧 Change default jmx port on deps.edn 2026-01-08 13:56:22 +01:00
Elena Torró b7eaeffa88
Merge pull request #8024 from penpot/azazeln28-issue-12835-fix-previous-styles-lost
🐛 Fix previous styles lost when changing selected text
2026-01-08 13:49:06 +01:00
Andrey Antukh 722fcc1f82 Merge remote-tracking branch 'origin/staging' into develop 2026-01-08 13:48:21 +01:00
Andrey Antukh b7cd315872 🐛 Fix wasm-playground on devenv 2026-01-08 13:48:09 +01:00
Andrés Moya 2ad42cfd9b
Add ability to remap tokens when renamed ones are referenced by other child tokens (#8035)
* 🎉 Add ability to remap tokens when renamed ones are referenced by other child tokens

Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com>

* 🐛 Fix remap skipping tokens with same name in different sets

* 📚 Update CHANGES.md

* 🔧 Fix css styles

---------

Signed-off-by: Akshay Gupta <gravity.akshay@gmail.com>
Co-authored-by: Akshay Gupta <gravity.akshay@gmail.com>
2026-01-08 13:42:06 +01:00
Eva Marco 743d4e5c8d
🐛 Fix error on shadow token creation (#8029) 2026-01-08 13:26:01 +01:00
Belén Albeza fb9560c315
🐛 Fix guides dropdown width (#8031)
* 🐛 Fix width of guides column dropdown

* ♻️ Remove deprecated tokens in css

* 🔧 Update changelog
2026-01-08 10:47:11 +01:00
Andrey Antukh 795f65632a 🐛 Fix wasm-playground on devenv 2026-01-08 10:42:37 +01:00
Alejandro Alonso d53c090900
Merge pull request #8028 from penpot/elenatorro-12956-fix-text-color-tokens
🐛 Fix missing text color token from selected shapes in selected colors list
2026-01-07 16:49:41 +01:00
Elena Torro 621e030095 🐛 Fix missing text color token from selected shapes in selected colors list 2026-01-07 16:41:25 +01:00
Alejandro Alonso 157e4aa2d0
Merge pull request #8025 from penpot/elenatorro-12951-fix-inner-text-shadow-token
🐛 Fix inner shadow selector on shadow token
2026-01-07 16:37:19 +01:00
Elena Torro 7cd2308f3b 🐛 Fix inner shadow selector on shadow token 2026-01-07 16:36:51 +01:00
Alejandro Alonso c315a15b48
Merge pull request #8026 from penpot/elenatorro-12997-fix-clojure-on-css-box-shadow
🐛 Fix CSS generated box-shadow property
2026-01-07 16:32:12 +01:00
Elena Torro 8a3e6d026e 🐛 Fix CSS generated box-shadow property 2026-01-07 16:28:05 +01:00
Florian Schrödl 0dd062d011
🐛 Fix line-height throwing for int (#7927) 2026-01-07 16:13:10 +01:00
Alejandro Alonso bfbb546699
Merge pull request #8027 from penpot/superalex-fix-colors-assets-from-shared-libraries
🐛 Fix color assets from shared libraries
2026-01-07 14:16:57 +01:00
Alejandro Alonso 083e77e9c5 🐛 Fix color assets from shared libraries 2026-01-07 14:02:28 +01:00
Aitor Moreno 7819e6c440 🐛 Fix previous styles lost when changing selected text 2026-01-07 12:41:39 +01:00
Andrey Antukh 952f622ce9 🔧 Add 'Reapply` prefix to valid commit checker prefixes 2026-01-07 11:56:38 +01:00
Andrey Antukh a6c6f97f47 Reapply "💄 Group tokens by name path (#7775)"
This reverts commit eff572d3bb.
2026-01-07 11:55:56 +01:00
Andrey Antukh 88424eb54a Merge branch 'staging' into develop 2026-01-07 11:55:40 +01:00
Alejandro Alonso 919f78daeb
Merge pull request #7965 from penpot/eva-fix-styles-on-viewer
🐛 Fix inspect tab styles on viewer
2026-01-07 11:54:33 +01:00
Eva Marco b5c30f8c41 🐛 Fix inspect tab styles on viewer 2026-01-07 11:41:49 +01:00
Alejandro Alonso 60aa426753
Merge pull request #8022 from penpot/alotor-fix-drag-handlers
🐛 Fix problem with dragging handlers
2026-01-07 11:36:26 +01:00
Alejandro Alonso 86f7d6b26b
Sanitizing error values (#8020) 2026-01-07 11:23:19 +01:00
Andrey Antukh 36732a4bd3
Make the devenv runtine initialization yarn independent (#8023) 2026-01-07 11:21:58 +01:00
Andrey Antukh eff572d3bb
Revert "💄 Group tokens by name path (#7775)"
This reverts commit 0956b66281.
2026-01-07 11:20:44 +01:00
alonso.torres d470d96833 🐛 Fix problem with dragging handlers 2026-01-07 11:00:02 +01:00
Aitor Moreno cab70773d2
Merge pull request #7667 from penpot/azazeln28-doc-add-more-info-text-editor-v2-readme
📚 Add more info about text editor v2
2026-01-07 09:40:06 +01:00
Alejandro Alonso de9a21121a Merge remote-tracking branch 'origin/staging' into develop 2026-01-05 13:22:14 +01:00
Alejandro Alonso 32ca42a093 Merge remote-tracking branch 'origin/staging-render' into staging 2026-01-05 13:21:58 +01:00
Alejandro Alonso 523a97a4ec
Merge pull request #8016 from penpot/alotor-fix-refresh-thumbnails
🐛 Fix problem with thumbnail regeneration
2026-01-05 13:21:34 +01:00
Alejandro Alonso 260f6861a3
Merge pull request #8015 from penpot/alotor-fix-grid-component-auto-sizing
🐛 Fix problem with grid layout components and auto sizing
2026-01-05 13:18:59 +01:00
alonso.torres edd53b419a 🐛 Fix problem with thumbnail regeneration 2026-01-05 13:09:40 +01:00
Alejandro Alonso cea10308b7 Merge remote-tracking branch 'origin/staging' into develop 2026-01-05 11:52:15 +01:00
alonso.torres 078a3d5a5c 🐛 Fix problem with grid layout components and auto sizing 2026-01-05 10:54:36 +01:00
Alejandro Alonso c4e57427ac Merge branch 'staging-render' into staging 2026-01-05 10:30:06 +01:00
David Barragán Merino 5223c9c881 🔧 Fix a typo in an interpolation 2026-01-05 09:13:14 +01:00
Alejandro Alonso be62fa10c4 📎 Bump new version on changelog 2026-01-05 08:42:57 +01:00
Alejandro Alonso 218f34380a
Merge pull request #8012 from penpot/azazeln28-refactor-minor-changes
♻️ Minor naming changes and event handling
2026-01-02 14:16:55 +01:00
Aitor Moreno 6c6b3db87e ♻️ Minor naming changes and event handling 2026-01-02 13:41:48 +01:00
Aitor Moreno e39f292499 📚 Add more info about text editor v2 2026-01-02 10:13:34 +01:00
231 changed files with 30199 additions and 27401 deletions

View File

@ -33,7 +33,7 @@ jobs:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }} MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: | TEXT: |
🐳 *[PENPOT] Docker image available: {{ github.ref_name }}* 🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra @infra

View File

@ -26,7 +26,7 @@ jobs:
- name: Check Commit Type - name: Check Commit Type
uses: gsactions/commit-message-checker@v2 uses: gsactions/commit-message-checker@v2
with: with:
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$' pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
flags: 'gm' flags: 'gm'
error: 'Commit should match CONTRIBUTING.md guideline' error: 'Commit should match CONTRIBUTING.md guideline'
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request

1
.gitignore vendored
View File

@ -5,6 +5,7 @@
!.yarn/releases !.yarn/releases
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
.pnpm-store
*-init.clj *-init.clj
*.css.json *.css.json
*.jar *.jar

View File

@ -1,5 +1,20 @@
# CHANGELOG # CHANGELOG
## 2.14.0 (Unreleased)
### :boom: Breaking changes & Deprecations
### :rocket: Epics and highlights
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
- Tokens panel nested path view [Taiga #9966](https://tree.taiga.io/project/penpot/us/9966)
### :bug: Bugs fixed
## 2.13.0 (Unreleased) ## 2.13.0 (Unreleased)
### :boom: Breaking changes & Deprecations ### :boom: Breaking changes & Deprecations
@ -21,7 +36,15 @@
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565) - 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) - Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339) - Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
## 2.12.1 ## 2.12.1
@ -31,7 +54,6 @@
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935) - Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917) - Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
## 2.12.0 ## 2.12.0
### :boom: Breaking changes & Deprecations ### :boom: Breaking changes & Deprecations
@ -43,7 +65,6 @@ The backend RPC API URLS are changed from `/api/rpc/command/<name>` to
compatibility; however, if you are a user of this API, it is strongly compatibility; however, if you are a user of this API, it is strongly
recommended that you adapt your code to use the new PATH. recommended that you adapt your code to use the new PATH.
#### Updated SSO Callback URL #### Updated SSO Callback URL
The OAuth / Single Sign-On (SSO) callback endpoint has changed to The OAuth / Single Sign-On (SSO) callback endpoint has changed to
@ -76,7 +97,6 @@ This update standardizes all authentication flows under the single URL
and makis it more modular, enabling the ability to configure SSO auth and makis it more modular, enabling the ability to configure SSO auth
provider dinamically. provider dinamically.
#### Changes on default docker compose #### Changes on default docker compose
We have updated the `docker/images/docker-compose.yaml` with a small We have updated the `docker/images/docker-compose.yaml` with a small

View File

@ -97,8 +97,8 @@
:jmx-remote :jmx-remote
{:jvm-opts ["-Dcom.sun.management.jmxremote" {:jvm-opts ["-Dcom.sun.management.jmxremote"
"-Dcom.sun.management.jmxremote.port=9090" "-Dcom.sun.management.jmxremote.port=9000"
"-Dcom.sun.management.jmxremote.rmi.port=9090" "-Dcom.sun.management.jmxremote.rmi.port=9000"
"-Dcom.sun.management.jmxremote.local.only=false" "-Dcom.sun.management.jmxremote.local.only=false"
"-Dcom.sun.management.jmxremote.authenticate=false" "-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false" "-Dcom.sun.management.jmxremote.ssl=false"

View File

@ -36,17 +36,6 @@
[integrant.core :as ig] [integrant.core :as ig]
[yetti.response :as-alias yres])) [yetti.response :as-alias yres]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OIDC PROVIDER (GENERIC) ;; OIDC PROVIDER (GENERIC)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -177,7 +166,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@ -222,7 +211,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@ -299,7 +288,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@ -341,7 +330,7 @@
:provider "gitlab" :provider "gitlab"
:base-uri (:base-uri provider) :base-uri (:base-uri provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
(ex/raise :type ::internal (ex/raise :type ::internal
@ -361,7 +350,7 @@
(l/inf :hint "provider initialized" (l/inf :hint "provider initialized"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))) :client-secret (d/obfuscate-string (:client-secret provider)))
provider) provider)
(catch Throwable cause (catch Throwable cause
@ -459,7 +448,7 @@
(l/trc :hint "fetch access token" (l/trc :hint "fetch access token"
:provider (:id provider) :provider (:id provider)
:client-id (:client-id provider) :client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)) :client-secret (d/obfuscate-string (:client-secret provider))
:grant-type (:grant_type params) :grant-type (:grant_type params)
:redirect-uri (:redirect_uri params)) :redirect-uri (:redirect_uri params))
@ -512,7 +501,7 @@
[cfg provider tdata] [cfg provider tdata]
(l/trc :hint "fetch user info" (l/trc :hint "fetch user info"
:uri (:user-uri provider) :uri (:user-uri provider)
:token (obfuscate-string (:token/access tdata))) :token (d/obfuscate-string (:token/access tdata)))
(let [params {:uri (:user-uri provider) (let [params {:uri (:user-uri provider)
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))} :headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}

View File

@ -1024,6 +1024,26 @@
:clj :clj
(sort comp-fn items)))) (sort comp-fn items))))
(defn obfuscate-string
"Obfuscates potentially sensitive values.
- One-arg arity:
* For strings shorter than 10 characters, all characters are replaced by `*`.
* For longer strings, the first 5 characters are preserved and the rest obfuscated.
- Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value
by `*`, preserving only the length."
([v]
(obfuscate-string v false))
([v full?]
(let [s (str v)
n (count s)]
(cond
(zero? n) s
full? (apply str (repeat n "*"))
(< n 10) (apply str (repeat n "*"))
:else (str (subs s 0 5)
(apply str (repeat (- n 5) "*")))))))
(defn reorder (defn reorder
"Reorder a vector by moving one of their items from some position to some space between positions. "Reorder a vector by moving one of their items from some position to some space between positions.
It clamps the position numbers to a valid range." It clamps the position numbers to a valid range."

View File

@ -10,6 +10,7 @@
(:refer-clojure :exclude [instance?]) (:refer-clojure :exclude [instance?])
(:require (:require
#?(:clj [clojure.stacktrace :as strace]) #?(:clj [clojure.stacktrace :as strace])
[app.common.data :refer [obfuscate-string]]
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.schema :as sm] [app.common.schema :as sm]
[clojure.core :as c] [clojure.core :as c]
@ -19,6 +20,10 @@
(:import (:import
clojure.lang.IPersistentMap))) clojure.lang.IPersistentMap)))
(def ^:private sensitive-fields
"Keys whose values must be obfuscated in validation explains."
#{:password :old-password :token :invitation-token})
#?(:clj (set! *warn-on-reflection* true)) #?(:clj (set! *warn-on-reflection* true))
(def ^:dynamic *data-length* 8) (def ^:dynamic *data-length* 8)
@ -110,7 +115,25 @@
(explain (:explain data) opts) (explain (:explain data) opts)
(contains? data ::sm/explain) (contains? data ::sm/explain)
(sm/humanize-explain (::sm/explain data) opts))) (let [exp (::sm/explain data)
sanitize-map (fn sanitize-map [m]
(reduce-kv
(fn [acc k v]
(let [k* (if (string? k) (keyword k) k)]
(cond
(contains? sensitive-fields k*)
(assoc acc k (if (map? v)
(sanitize-map v)
(obfuscate-string v true)))
(map? v) (assoc acc k (sanitize-map v))
:else (assoc acc k v))))
{}
m))
sanitize-explain (fn [exp]
(cond-> exp
(:value exp) (update :value sanitize-map)))]
(sm/humanize-explain (sanitize-explain exp) opts))))
#?(:clj #?(:clj
(defn format-throwable (defn format-throwable

View File

@ -269,8 +269,8 @@
"Remove flex children properties except the fit-content for flex layouts. These are properties "Remove flex children properties except the fit-content for flex layouts. These are properties
that we don't have to propagate to copies but will be respected when swapping components" that we don't have to propagate to copies but will be respected when swapping components"
[shape] [shape]
(let [layout-item-h-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-width? shape)) :auto) (let [layout-item-h-sizing (when (and (ctl/any-layout? shape) (ctl/auto-width? shape)) :auto)
layout-item-v-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-height? shape)) :auto)] layout-item-v-sizing (when (and (ctl/any-layout? shape) (ctl/auto-height? shape)) :auto)]
(-> shape (-> shape
(d/without-keys ctk/swap-keep-attrs) (d/without-keys ctk/swap-keep-attrs)
(cond-> (some? layout-item-h-sizing) (cond-> (some? layout-item-h-sizing)

View File

@ -112,8 +112,10 @@
(:c2y params) (update-in [index :params :c2y] + (:c2y params))) (:c2y params) (update-in [index :params :c2y] + (:c2y params)))
content))] content))]
(if (some? modifiers)
(impl/path-data (impl/path-data
(reduce apply-to-index (vec content) modifiers)))) (reduce apply-to-index (vec content) modifiers))
content)))
(defn transform-content (defn transform-content
"Applies a transformation matrix over content and returns a new "Applies a transformation matrix over content and returns a new

View File

@ -47,6 +47,18 @@
self-reference? (get token-references token-name)] self-reference? (get token-references token-name)]
self-reference?)) self-reference?))
(defn references-token?
"Recursively check if a value references the token name. Handles strings, maps, and sequences."
[value token-name]
(cond
(string? value)
(boolean (some #(= % token-name) (find-token-value-references value)))
(map? value)
(some true? (map #(references-token? % token-name) (vals value)))
(sequential? value)
(some true? (map #(references-token? % token-name) value))
:else false))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMA ;; SCHEMA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -558,3 +570,18 @@
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token." "Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
[token-value] [token-value]
(string? token-value)) (string? token-value))
(defn update-token-value-references
"Recursively update token references within a token value, supporting complex token values (maps, sequences, strings)."
[value old-name new-name]
(cond
(string? value)
(str/replace value
(re-pattern (str "\\{" (str/replace old-name "." "\\.") "\\}"))
(str "{" new-name "}"))
(map? value)
(d/update-vals value #(update-token-value-references % old-name new-name))
(sequential? value)
(mapv #(update-token-value-references % old-name new-name) value)
:else
value))

View File

@ -909,7 +909,8 @@ Will return a value that matches this schema:
`:all` All of the nested sets are active `:all` All of the nested sets are active
`:partial` Mixed active state of nested sets") `:partial` Mixed active state of nested sets")
(get-tokens-in-active-sets [_] "set of set names that are active in the the active themes") (get-tokens-in-active-sets [_] "set of set names that are active in the the active themes")
(get-all-tokens [_] "all tokens in the lib") (get-all-tokens [_] "all tokens in the lib, as a sequence")
(get-all-tokens-map [_] "all tokens in the lib, as a map name -> token")
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name")) (get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name"))
(declare parse-multi-set-dtcg-json) (declare parse-multi-set-dtcg-json)
@ -1306,6 +1307,10 @@ Will return a value that matches this schema:
tokens)) tokens))
(get-all-tokens [this] (get-all-tokens [this]
(mapcat #(vals (get-tokens- %))
(get-sets this)))
(get-all-tokens-map [this]
(reduce (reduce
(fn [tokens' set] (fn [tokens' set]
(into tokens' (map (fn [x] [(:name x) x]) (vals (get-tokens- set))))) (into tokens' (map (fn [x] [(:name x) x]) (vals (get-tokens- set)))))

View File

@ -41,7 +41,10 @@ services:
- 6062:6062 - 6062:6062
- 6063:6063 - 6063:6063
- 6064:6064 - 6064:6064
- 9000:9000
- 9001:9001
- 9090:9090 - 9090:9090
- 9091:9091
environment: environment:
- EXTERNAL_UID=${CURRENT_USER_ID} - EXTERNAL_UID=${CURRENT_USER_ID}

View File

@ -145,8 +145,8 @@ http {
proxy_pass http://127.0.0.1:3000/; proxy_pass http://127.0.0.1:3000/;
} }
location /playground { location /wasm-playground {
alias /home/penpot/penpot/experiments/; alias /home/penpot/penpot/frontend/resources/public/wasm-playground/;
add_header Cache-Control "no-cache, max-age=0"; add_header Cache-Control "no-cache, max-age=0";
autoindex on; autoindex on;
} }

View File

@ -8,14 +8,10 @@ source ~/.bashrc
echo "[start-tmux.sh] Installing node dependencies" echo "[start-tmux.sh] Installing node dependencies"
pushd ~/penpot/frontend/ pushd ~/penpot/frontend/
corepack install; ./scripts/setup;
yarn install;
yarn playwright install chromium
popd popd
pushd ~/penpot/exporter/ pushd ~/penpot/exporter/
corepack install; ./scripts/setup;
yarn install
yarn playwright install chromium
popd popd
tmux -2 new-session -d -s penpot tmux -2 new-session -d -s penpot

8
exporter/scripts/setup Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e;
corepack enable;
corepack install;
yarn install;
yarn playwright install chromium

View File

@ -198,10 +198,10 @@ export class WorkspacePage extends BaseWebSocketPage {
`[id="shape-00000000-0000-0000-0000-000000000000"]`, `[id="shape-00000000-0000-0000-0000-000000000000"]`,
); );
this.toolbarOptions = page.getByTestId("toolbar-options"); this.toolbarOptions = page.getByTestId("toolbar-options");
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" }); this.rectShapeButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Rectangle" });
this.ellipseShapeButton = page.getByRole("button", { name: "Ellipse (E)" }); this.ellipseShapeButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Ellipse" });
this.moveButton = page.getByRole("button", { name: "Move (V)" }); this.moveButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Move" });
this.boardButton = page.getByRole("button", { name: "Board (B)" }); this.boardButton = page.getByTestId("toolbar-options").getByRole("button", { name: "Board" });
this.toggleToolbarButton = page.getByRole("button", { this.toggleToolbarButton = page.getByRole("button", {
name: "Toggle toolbar", name: "Toggle toolbar",
}); });

View File

@ -189,8 +189,8 @@ test("BUG 7760 - Layout losing properties when changing parents", async ({
await workspacePage.clickLeafLayer("Flex Board"); await workspacePage.clickLeafLayer("Flex Board");
// Move the first board into the second // Move the first board into the second
const hAuto = await workspacePage.page.getByTitle("Fit content (Horizontal)"); const hAuto = await workspacePage.page.getByTestId("behaviour-h-auto");
const vAuto = await workspacePage.page.getByTitle("Fit content (Vertical)"); const vAuto = await workspacePage.page.getByTestId("behaviour-v-auto");
await expect(vAuto.locator("input")).toBeChecked(); await expect(vAuto.locator("input")).toBeChecked();
await expect(hAuto.locator("input")).toBeChecked(); await expect(hAuto.locator("input")).toBeChecked();

View File

@ -305,7 +305,7 @@ test.describe("Inspect tab - Styles", () => {
); );
await openInspectTab(workspacePage); await openInspectTab(workspacePage);
const panel = await getPanelByTitle(workspacePage, "Size & position"); const panel = await getPanelByTitle(workspacePage, "Size and position");
await expect(panel).toBeVisible(); await expect(panel).toBeVisible();
const propertyRow = panel.getByTestId("property-row"); const propertyRow = panel.getByTestId("property-row");
@ -335,7 +335,7 @@ test.describe("Inspect tab - Styles", () => {
); );
await openInspectTab(workspacePage); await openInspectTab(workspacePage);
const panel = await getPanelByTitle(workspacePage, "Size & position"); const panel = await getPanelByTitle(workspacePage, "Size and position");
await expect(panel).toBeVisible(); await expect(panel).toBeVisible();
const propertyRow = panel.getByTestId("property-row"); const propertyRow = panel.getByTestId("property-row");
@ -375,7 +375,7 @@ test.describe("Inspect tab - Styles", () => {
); );
await openInspectTab(workspacePage); await openInspectTab(workspacePage);
const panel = await getPanelByTitle(workspacePage, "Size & position"); const panel = await getPanelByTitle(workspacePage, "Size and position");
await expect(panel).toBeVisible(); await expect(panel).toBeVisible();
const propertyRow = panel.getByTestId("property-row"); const propertyRow = panel.getByTestId("property-row");

View File

@ -2418,10 +2418,12 @@ test.describe("Tokens: Apply token", () => {
await nameField.fill(newTokenTitle); await nameField.fill(newTokenTitle);
const referenceTabButton = const referenceTabButton =
tokensUpdateCreateModal.getByTestId("reference-opt"); tokensUpdateCreateModal.getByRole('button', { name: 'Use a reference' });
referenceTabButton.click(); referenceTabButton.click();
const referenceField = tokensUpdateCreateModal.getByLabel("Reference"); const referenceField = tokensUpdateCreateModal.getByRole('textbox', {
name: 'Reference'
});
await referenceField.fill("{Full}"); await referenceField.fill("{Full}");
const submitButton = tokensUpdateCreateModal.getByRole("button", { const submitButton = tokensUpdateCreateModal.getByRole("button", {
@ -2740,3 +2742,626 @@ test.describe("Tokens: Apply token", () => {
}); });
}); });
}); });
test.describe("Tokens: Remapping Feature", () => {
test.describe("Box Shadow Token Remapping", () => {
test("User renames box shadow token with alias references", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base shadow token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("base-shadow");
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("#000000");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Create derived shadow token that references base-shadow
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
await nameField.fill("derived-shadow");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
await referenceField.fill("{base-shadow}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Rename base-shadow token
const baseToken = tokensSidebar.getByRole("button", {
name: "base-shadow",
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("foundation-shadow");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
await expect(remappingModal).toContainText("1");
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
});
await confirmButton.click();
// Verify token was renamed
await expect(
tokensSidebar.getByRole("button", { name: "foundation-shadow" }),
).toBeVisible();
await expect(
tokensSidebar.getByRole("button", { name: "derived-shadow" }),
).toBeVisible();
});
test("User renames and updates shadow token - referenced token and applied shapes update", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
workspacePage,
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base shadow token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("primary-shadow");
let colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("#000000");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Create derived shadow token that references base
await tokensTabPanel
.getByRole("button", { name: "Add Token: Shadow" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("card-shadow");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
await referenceField.fill("{primary-shadow}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Apply the referenced token to a shape
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Button" })
.click();
await page.getByRole("tab", { name: "Tokens" }).click();
const cardShadowToken = tokensSidebar.getByRole("button", {
name: "card-shadow",
});
await cardShadowToken.click();
// Rename and update value of base token
const primaryToken = tokensSidebar.getByRole("button", {
name: "primary-shadow",
});
await primaryToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("main-shadow");
// Update the color value
colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("#FF0000");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
// Confirm remapping
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
});
await confirmButton.click();
// Verify base token was renamed
await expect(
tokensSidebar.getByRole("button", { name: "main-shadow" }),
).toBeVisible();
// Verify referenced token still exists
await expect(
tokensSidebar.getByRole("button", { name: "card-shadow" }),
).toBeVisible();
// Verify the shape still has the token applied with the NEW name
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Button" })
.click();
// Verify the shape still has the shadow applied with the UPDATED color value
// Expand the shadow section to access the color field
const shadowSection = workspacePage.rightSidebar.getByTestId("shadow-section");
await expect(shadowSection).toBeVisible();
// Click to expand the shadow options (the menu button)
const shadowMenuButton = shadowSection
.getByRole("button", { name: "options" })
.first();
await shadowMenuButton.click();
// Wait for the advanced options to appear
await page.waitForTimeout(500);
// Verify the color value has updated from #000000 to #FF0000
const colorInput = shadowSection.getByRole("textbox", { name: "Color" });
expect(colorInput).not.toBeNull();
const colorValue = await colorInput.inputValue();
expect(colorValue.toUpperCase()).toBe("FF0000");
});
});
test.describe("Typography Token Remapping", () => {
test("User renames typography token with alias references", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTypographyTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("base-text");
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("16");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Create derived typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
await nameField.fill("body-text");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"})
await referenceField.fill("{base-text}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Rename base token
const baseToken = tokensSidebar.getByRole("button", {
name: "base-text",
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("default-text");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
});
await confirmButton.click();
// Verify token was renamed
await expect(
tokensSidebar.getByRole("button", { name: "default-text" }),
).toBeVisible();
await expect(
tokensSidebar.getByRole("button", { name: "body-text" }),
).toBeVisible();
});
test("User renames and updates typography token - referenced token and applied shapes update", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
workspacePage,
} = await setupTypographyTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("body-style");
let fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("16");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Create derived typography token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByRole("textbox", {name: "Name"});
await nameField.fill("paragraph-style");
const referenceToggle =
tokensUpdateCreateModal.getByTestId("reference-opt");
await referenceToggle.click();
const referenceField = tokensUpdateCreateModal.getByRole("textbox", {name: "Reference"});
await referenceField.fill("{body-style}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Apply the referenced token to a text shape
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Some Text" })
.click();
await page.getByRole("tab", { name: "Tokens" }).click();
const paragraphToken = tokensSidebar.getByRole("button", {
name: "paragraph-style",
});
await paragraphToken.click();
// Rename and update value of base token
const bodyToken = tokensSidebar.getByRole("button", {
name: "body-style",
});
await bodyToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("text-base");
// Update the font size value
fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("18");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
// Confirm remapping
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
});
await confirmButton.click();
// Verify base token was renamed
await expect(
tokensSidebar.getByRole("button", { name: "text-base" }),
).toBeVisible();
// Verify referenced token still exists
await expect(
tokensSidebar.getByRole("button", { name: "paragraph-style" }),
).toBeVisible();
// Verify the text shape still has the token applied with NEW name and value
await page.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Some Text" })
.click();
// Verify the shape shows the updated font size value (18)
// This proves the remapping worked and the value update propagated through the reference
const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", {
name: "Font Size",
});
await expect(fontSizeInput).toBeVisible();
await expect(fontSizeInput).toHaveValue("18");
});
});
test.describe("Border Radius Token Remapping", () => {
test("User renames border radius token with alias references", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("base-radius");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.fill("4");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Create derived border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("card-radius");
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
await valueField2.fill("{base-radius}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Rename base token
const baseToken = tokensSidebar.getByRole("button", {
name: "base-radius",
});
await baseToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("primary-radius");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
// Check for remapping modal
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
});
await confirmButton.click();
// Verify token was renamed
await expect(
tokensSidebar.getByRole("button", { name: "primary-radius" }),
).toBeVisible();
await expect(
tokensSidebar.getByRole("button", { name: "card-radius" }),
).toBeVisible();
});
test("User renames and updates border radius token - referenced token updates", async ({
page,
}) => {
const {
tokensUpdateCreateModal,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
// Create base border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
let nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("radius-sm");
let valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.fill("4");
let submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Create derived border radius token
await tokensTabPanel
.getByRole("button", { name: "Add Token: Border Radius" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("button-radius");
const valueField2 = tokensUpdateCreateModal.getByLabel("Value");
await valueField2.fill("{radius-sm}");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Rename and update value of base token
const radiusToken = tokensSidebar.getByRole("button", {
name: "radius-sm",
});
await radiusToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("radius-base");
// Update the value
valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.fill("8");
submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await submitButton.click();
// Confirm remapping
const remappingModal = page.getByTestId("token-remapping-modal");
await expect(remappingModal).toBeVisible({ timeout: 5000 });
const confirmButton = remappingModal.getByRole("button", {
name: /remap/i,
});
await confirmButton.click();
// Verify base token was renamed
await expect(
tokensSidebar.getByRole("button", { name: "radius-base" }),
).toBeVisible();
// Verify referenced token still exists
await expect(
tokensSidebar.getByRole("button", { name: "button-radius" }),
).toBeVisible();
// Verify the referenced token now points to the renamed token
// by opening it and checking the reference
const buttonRadiusToken = tokensSidebar.getByRole("button", {
name: "button-radius",
});
await buttonRadiusToken.click({ button: "right" });
await tokenContextMenuForToken.getByText("Edit token").click();
await expect(tokensUpdateCreateModal).toBeVisible();
const currentValue = tokensUpdateCreateModal.getByLabel("Value");
await expect(currentValue).toHaveValue("{radius-base}");
});
});
});

View File

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

View File

@ -667,6 +667,9 @@
} }
// UI ELEMENTS // UI ELEMENTS
// FIXME: This is used multiple times accross the app. We should design this in
// the DS and create a proper component for it.
.asset-element { .asset-element {
@include bodySmallTypography; @include bodySmallTypography;
display: flex; display: flex;

View File

@ -245,13 +245,6 @@
--assets-component-second-border-selected: var(--color-background-primary); --assets-component-second-border-selected: var(--color-background-primary);
--assets-component-hightlight: var(--color-accent-secondary); --assets-component-hightlight: var(--color-accent-secondary);
--radio-btns-background-color: var(--color-background-tertiary);
--radio-btn-background-color-selected: var(--color-background-quaternary);
--radio-btn-foreground-color: var(--color-foreground-secondary);
--radio-btn-foreground-color-selected: var(--color-accent-primary);
--radio-btn-border-color: var(--color-background-tertiary);
--radio-btn-border-color-selected: var(--color-background-quaternary);
--library-name-foreground-color: var(--color-foreground-primary); --library-name-foreground-color: var(--color-foreground-primary);
--library-content-foreground-color: var(--color-foreground-secondary); --library-content-foreground-color: var(--color-foreground-secondary);
@ -424,13 +417,6 @@
--tab-border-color: var(--color-background-tertiary); --tab-border-color: var(--color-background-tertiary);
--tab-border-color-selected: var(--color-background-secondary); --tab-border-color-selected: var(--color-background-secondary);
--radio-btns-background-color: var(--color-background-tertiary);
--radio-btn-background-color-selected: var(--color-background-primary);
--radio-btn-foreground-color: var(--color-foreground-secondary);
--radio-btn-foreground-color-selected: var(--color-accent-primary);
--radio-btn-border-color: var(--color-background-tertiary);
--radio-btn-border-color-selected: var(--color-background-secondary);
--button-icon-background-color-selected: var(--color-background-primary); --button-icon-background-color-selected: var(--color-background-primary);
--button-icon-border-color-selected: var(--color-background-secondary); --button-icon-border-color-selected: var(--color-background-secondary);

6
frontend/scripts/setup Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
corepack enable;
corepack install;
yarn install;
yarn playwright install chromium;

View File

@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -ex SCRIPT_DIR=$(dirname $0);
corepack enable;
corepack install; set -ex
yarn install;
$SCRIPT_DIR/setup;
yarn run playwright install chromium --with-deps;
yarn run build:storybook yarn run build:storybook
yarn run test:storybook yarn run test:storybook

View File

@ -1,8 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
SCRIPT_DIR=$(dirname $0);
set -ex set -ex
corepack enable;
corepack install; $SCRIPT_DIR/setup;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run test:e2e -x --workers=2 --reporter=list "$@"; yarn run test:e2e -x --workers=2 --reporter=list "$@";

View File

@ -236,7 +236,7 @@
Uses `font-size-value` to calculate the relative line-height value. Uses `font-size-value` to calculate the relative line-height value.
Returns an error for an invalid font-size value." Returns an error for an invalid font-size value."
[line-height-value font-size-value font-size-errors] [line-height-value font-size-value font-size-errors]
(let [missing-references (seq (some cto/find-token-value-references line-height-value)) (let [missing-references (seq (cto/find-token-value-references line-height-value))
error error
(cond (cond
missing-references missing-references

View File

@ -432,10 +432,12 @@
:val position-data :val position-data
:ignore-touched true :ignore-touched true
:ignore-geometry true}]})))] :ignore-geometry true}]})))]
(when (d/not-empty? changes)
(dch/commit-changes (dch/commit-changes
{:redo-changes changes :undo-changes [] {:redo-changes changes :undo-changes []
:save-undo? false :save-undo? false
:tags #{:position-data}}))))))) :tags #{:position-data}})))))
(rx/take-until stoper-s))))
(->> stream (->> stream
(rx/filter dch/commit?) (rx/filter dch/commit?)

View File

@ -1122,7 +1122,7 @@
ref-id (:stroke-color-ref-id stroke) ref-id (:stroke-color-ref-id stroke)
colors (-> libraries colors (-> libraries
(get ref-id) (get ref-file)
(get :data) (get :data)
(ctl/get-colors)) (ctl/get-colors))
shared? (contains? colors ref-id) shared? (contains? colors ref-id)
@ -1167,7 +1167,7 @@
ref-file (get color :ref-file) ref-file (get color :ref-file)
ref-id (get color :ref-id) ref-id (get color :ref-id)
colors (-> libraries colors (-> libraries
(get ref-id) (get ref-file)
(get :data) (get :data)
(ctl/get-colors)) (ctl/get-colors))
shared? (contains? colors ref-id) shared? (contains? colors ref-id)
@ -1180,19 +1180,20 @@
:index (:index shadow)})) :index (:index shadow)}))
(defn- text->color-att (defn- text->color-att
[fill file-id libraries] [fill file-id libraries & {:keys [has-token-applied token-name]}]
(let [ref-file (:fill-color-ref-file fill) (let [ref-file (:fill-color-ref-file fill)
ref-id (:fill-color-ref-id fill) ref-id (:fill-color-ref-id fill)
colors (-> libraries colors (-> libraries
(get ref-id) (get ref-file)
(get :data) (get :data)
(ctl/get-colors)) (ctl/get-colors))
shared? (contains? colors ref-id) shared? (contains? colors ref-id)
attrs (cond-> (types.fills/fill->color fill) base-attrs (cond-> (types.fills/fill->color fill)
(not (or shared? (= ref-file file-id))) (not (or shared? (= ref-file file-id)))
(dissoc :ref-file :ref-id))] (dissoc :ref-file :ref-id))
attrs (cond-> base-attrs
has-token-applied (assoc :has-token-applied true)
token-name (assoc :token-name token-name))]
{:attrs attrs {:attrs attrs
:prop :content :prop :content
:shape-id (:shape-id fill) :shape-id (:shape-id fill)
@ -1200,13 +1201,18 @@
(defn- extract-text-colors (defn- extract-text-colors
[text file-id libraries] [text file-id libraries]
(let [treat-node (let [applied-fill-token (get-in text [:applied-tokens :fill])
treat-node
(fn [node shape-id] (fn [node shape-id]
(map-indexed #(assoc %2 :shape-id shape-id :index %1) node))] (map-indexed (fn [idx fill]
(let [args (cond-> []
(and (= idx 0) applied-fill-token)
(conj :has-token-applied true :token-name applied-fill-token))]
(apply text->color-att (assoc fill :shape-id shape-id :index idx) file-id libraries args)))
node))]
(->> (txt/node-seq txt/is-text-node? (:content text)) (->> (txt/node-seq txt/is-text-node? (:content text))
(map :fills) (map :fills)
(mapcat #(treat-node % (:id text))) (mapcat #(treat-node % (:id text))))))
(map #(text->color-att % file-id libraries)))))
(defn- fill->color-att (defn- fill->color-att
"Given a fill map enriched with :shape-id, :index, and optionally "Given a fill map enriched with :shape-id, :index, and optionally
@ -1232,7 +1238,7 @@
ref-id (:fill-color-ref-id fill) ref-id (:fill-color-ref-id fill)
colors (-> libraries colors (-> libraries
(get ref-id) (get ref-file)
(get :data) (get :data)
(ctl/get-colors)) (ctl/get-colors))
shared? (contains? colors ref-id) shared? (contains? colors ref-id)

View File

@ -47,16 +47,15 @@
(ptk/reify ::apply-content-modifiers (ptk/reify ::apply-content-modifiers
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [page-id (get state :current-page-id state) (let [id (st/get-path-id state)
objects (dsh/lookup-page-objects state) shape (st/get-path state)
id (st/get-path-id state)
shape
(st/get-path state)
content-modifiers content-modifiers
(dm/get-in state [:workspace-local :edit-path id :content-modifiers]) (dm/get-in state [:workspace-local :edit-path id :content-modifiers])]
(if (or (nil? shape) (nil? content-modifiers))
(rx/of (dwe/clear-edition-mode))
(let [page-id (get state :current-page-id state)
objects (dsh/lookup-page-objects state)
content (get shape :content) content (get shape :content)
new-content (path/apply-content-modifiers content content-modifiers) new-content (path/apply-content-modifiers content content-modifiers)
@ -72,7 +71,7 @@
(dwe/clear-edition-mode)) (dwe/clear-edition-mode))
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(selection/update-selection point-change) (selection/update-selection point-change)
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler)))))))))) (fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))))
(defn modify-content-point (defn modify-content-point
[content {dx :x dy :y} modifiers point] [content {dx :x dy :y} modifiers point]

View File

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

View File

@ -11,6 +11,7 @@
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.logic.tokens :as clt] [app.common.logic.tokens :as clt]
[app.common.path-names :as cpn]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
@ -22,6 +23,7 @@
[app.main.data.workspace.tokens.propagation :as dwtp] [app.main.data.workspace.tokens.propagation :as dwtp]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
(declare set-selected-token-set-id) (declare set-selected-token-set-id)
@ -460,12 +462,35 @@
;; TOKEN UI OPS ;; TOKEN UI OPS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn set-token-type-section-open (defn clean-tokens-paths
[token-type open?] []
(ptk/reify ::set-token-type-section-open (ptk/reify ::clean-tokens-paths
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:workspace-tokens :open-status-by-type] assoc token-type open?)))) (assoc-in state [:workspace-tokens :unfolded-token-paths] []))))
(defn toggle-token-path
[path]
(ptk/reify ::toggle-token-path
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-tokens :unfolded-token-paths]
(fn [paths]
(let [paths (or paths [])]
(if (some #(= % path) paths)
(vec (remove #(or (= % path)
(str/starts-with? % (str path ".")))
paths))
(let [split-path (cpn/split-path path :separator ".")
partial-paths (reduce
(fn [acc segment]
(let [new-acc (if (empty? acc)
segment
(str (last acc) "." segment))]
(conj acc new-acc)))
[]
split-path)]
(into paths partial-paths)))))))))
(defn assign-token-context-menu (defn assign-token-context-menu
[{:keys [position] :as params}] [{:keys [position] :as params}]

View File

@ -22,6 +22,9 @@
[clojure.set :as set] [clojure.set :as set]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(l/set-level! :warn)
;; Helpers --------------------------------------------------------------------- ;; Helpers ---------------------------------------------------------------------
;; TODO: see if this can be replaced by more standard functions ;; TODO: see if this can be replaced by more standard functions

View File

@ -0,0 +1,177 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.data.workspace.tokens.remapping
"Core logic for token remapping functionality"
(:require
[app.common.files.changes-builder :as pcb]
[app.common.files.tokens :as cft]
[app.common.logging :as log]
[app.common.types.container :refer [shapes-seq]]
[app.common.types.file :refer [object-containers-seq]]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dh]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
;; Token Reference Scanning
;; ========================
(defn scan-shape-applied-tokens
"Scan a shape for applied token references to a specific token name"
[shape token-name container]
(when-let [applied-tokens (:applied-tokens shape)]
(for [[attribute applied-token-name] applied-tokens
:when (= applied-token-name token-name)]
{:type :applied-token
:shape-id (:id shape)
:attribute attribute
:token-name applied-token-name
:container container})))
(defn scan-token-value-references
"Scan a token value for references to a specific token name (alias), supporting complex token values."
[token token-name]
(letfn [(find-all-token-value-references [token-value]
(cond
(string? token-value)
(filter #(= % token-name) (cto/find-token-value-references token-value))
(map? token-value)
(mapcat find-all-token-value-references (vals token-value))
(sequential? token-value)
(mapcat find-all-token-value-references token-value)
:else
[]))]
(when-let [value (:value token)]
(for [referenced-token-name (find-all-token-value-references value)]
{:type :token-alias
:source-token-id (:id token)
:referenced-token-name referenced-token-name}))))
(defn scan-workspace-token-references
"Scan entire workspace for all token references to a specific token"
[file-data old-token-name]
(let [tokens-lib (:tokens-lib file-data)
containers (object-containers-seq file-data)
;; Scan all shapes for applied token references to the specific token
matching-applied (mapcat (fn [container]
(let [shapes (shapes-seq container)]
(mapcat #(scan-shape-applied-tokens % old-token-name container) shapes)))
containers)
;; Scan tokens library for alias references to the specific token
matching-aliases (if tokens-lib
(let [all-tokens (ctob/get-all-tokens tokens-lib)]
(mapcat #(scan-token-value-references % old-token-name) all-tokens))
[])]
(log/info :hint "token-scan-details"
:token-name old-token-name
:containers-count (count containers)
:total-applied-refs (count matching-applied)
:matching-applied (count matching-applied)
:total-alias-refs (count matching-aliases)
:matching-aliases (count matching-aliases))
{:applied-tokens matching-applied
:token-aliases matching-aliases
:total-references (+ (count matching-applied) (count matching-aliases))}))
;; Token Remapping Core Logic
;; ==========================
(defn remap-tokens
"Main function to remap all token references when a token name changes"
[old-token-name new-token-name]
(ptk/reify ::remap-tokens
ptk/WatchEvent
(watch [_ state _]
(let [file-data (dh/lookup-file-data state)
scan-results (scan-workspace-token-references file-data old-token-name)
tokens-lib (:tokens-lib file-data)
sets (ctob/get-sets tokens-lib)
tokens-with-sets (mapcat (fn [set]
(map (fn [token]
{:token token :set set})
(vals (ctob/get-tokens tokens-lib (ctob/get-id set)))))
sets)
;; Group applied token references by container
refs-by-container (group-by :container (:applied-tokens scan-results))
;; Use apply-token logic to update shapes for both direct and alias references
shape-changes (reduce-kv
(fn [changes container refs]
(let [shape-ids (map :shape-id refs)
;; Find the correct token to apply (new or alias)
token (or (some #(when (= (:name (:token %)) new-token-name) %) tokens-with-sets)
(some #(when (= (:name (:token %)) old-token-name) %) tokens-with-sets))
attributes (set (map :attribute refs))]
(if token
(-> (pcb/with-container changes container)
(pcb/update-shapes shape-ids
(fn [shape]
(update shape :applied-tokens
#(merge % (cft/attributes-map attributes (:token token)))))))
changes)))
(-> (pcb/empty-changes)
(pcb/with-file-data file-data)
(pcb/with-library-data file-data))
refs-by-container)
;; Create changes for updating token alias references
token-changes (reduce
(fn [changes ref]
(let [source-token-id (:source-token-id ref)]
(when-let [{:keys [token set]} (some #(when (= (:id (:token %)) source-token-id) %) tokens-with-sets)]
(let [old-value (:value token)
new-value (cto/update-token-value-references old-value old-token-name new-token-name)]
(pcb/set-token changes (ctob/get-id set) (:id token)
(assoc token :value new-value))))))
shape-changes
(:token-aliases scan-results))]
(log/info :hint "token-remapping"
:old-name old-token-name
:new-name new-token-name
:references-count (:total-references scan-results))
(rx/of (dch/commit-changes token-changes))))))
(defn validate-token-remapping
"Validate that a token remapping operation is safe to perform"
[old-name new-name]
(cond
(str/blank? new-name)
{:valid? false
:error :invalid-name
:message "Token name cannot be empty"}
(= old-name new-name)
{:valid? false
:error :no-change
:message "New name is the same as current name"}
:else
{:valid? true}))
(defn count-token-references
"Count the number of references to a token in the workspace"
[file-data token-name]
(let [scan-results (scan-workspace-token-references file-data token-name)]
(log/info :hint "token-reference-scan"
:token-name token-name
:applied-refs (count (:applied-tokens scan-results))
:alias-refs (count (:token-aliases scan-results))
:total (:total-references scan-results))
(:total-references scan-results)))

View File

@ -4,22 +4,29 @@
// //
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; // FIXME: we need this import for .asset-element
@use "refactor/basic-rules.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/spacing.scss" as *;
.editable-select { .editable-select {
@extend .asset-element; @extend .asset-element;
margin: 0; margin: 0;
padding: 0; padding: 0;
border: deprecated.$s-1 solid var(--input-border-color); border: $b-1 solid var(--input-border-color);
position: relative; position: relative;
display: flex; display: flex;
height: deprecated.$s-32; height: $sz-32;
width: 100%; width: 100%;
padding: deprecated.$s-8; padding: var(--sp-s);
border-radius: deprecated.$br-8; border-radius: $br-8;
cursor: pointer; cursor: pointer;
.dropdown-button { .dropdown-button {
@include deprecated.flexCenter; display: flex;
place-content: center;
svg { svg {
@extend .button-icon-small; @extend .button-icon-small;
transform: rotate(90deg); transform: rotate(90deg);
@ -29,10 +36,11 @@
.custom-select-dropdown { .custom-select-dropdown {
@extend .dropdown-wrapper; @extend .dropdown-wrapper;
max-height: deprecated.$s-320; width: fit-content;
max-height: px2rem(320); // TODO: when this gets addressed in the DS, use a token
.separator { .separator {
margin: 0; margin: 0;
height: deprecated.$s-12; height: $sz-12;
} }
.dropdown-element { .dropdown-element {
@extend .dropdown-element-base; @extend .dropdown-element-base;
@ -43,7 +51,8 @@
} }
.check-icon { .check-icon {
@include deprecated.flexCenter; display: flex;
place-content: center;
svg { svg {
@extend .button-icon-small; @extend .button-icon-small;
visibility: hidden; visibility: hidden;

View File

@ -10,9 +10,9 @@
[app.util.dom :as dom] [app.util.dom :as dom]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc file-uploader (mf/defc file-uploader*
{::mf/forward-ref true} {::mf/forward-ref true}
[{:keys [accept multi label-text label-class input-id on-selected data-testid] :as props} input-ref] [{:keys [accept multi label-text label-class input-id on-selected data-testid]} input-ref]
(let [opt-pick-one #(if multi % (first %)) (let [opt-pick-one #(if multi % (first %))
on-files-selected on-files-selected

View File

@ -315,7 +315,8 @@
gap: deprecated.$s-4; gap: deprecated.$s-4;
max-height: deprecated.$s-136; max-height: deprecated.$s-136;
padding: deprecated.$s-4 0; padding: deprecated.$s-4 0;
overflow-y: scroll; overflow-y: auto;
.selected-item { .selected-item {
.around { .around {
@include deprecated.flexRow; @include deprecated.flexRow;

View File

@ -1,107 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.components.radio-buttons
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
[app.main.ui.formats :as fmt]
[app.util.dom :as dom]
[rumext.v2 :as mf]))
(def context
(mf/create-context nil))
(mf/defc radio-button
{::mf/props :obj}
[{:keys [icon id value disabled title icon-class type]}]
(let [context (mf/use-ctx context)
allow-empty (unchecked-get context "allow-empty")
type (if ^boolean type
type
(if ^boolean allow-empty
"checkbox"
"radio"))
on-change (unchecked-get context "on-change")
selected (unchecked-get context "selected")
name (unchecked-get context "name")
encode-fn (unchecked-get context "encode-fn")
checked? (= selected value)
value (encode-fn value)]
[:label {:html-for id
:data-testid id
:title title
:class (stl/css-case
:radio-icon true
:checked checked?
:disabled disabled)}
(if (some? icon)
[:> icon* {:icon-id icon :class icon-class :aria-hidden true}]
[:span {:class (stl/css :title-name)} value])
[:input {:id id
:on-change on-change
:type type
:name name
:disabled disabled
:value value
:default-checked checked?}]]))
(mf/defc radio-buttons
{::mf/props :obj}
[{:keys [name children on-change selected class wide encode-fn decode-fn allow-empty] :as props}]
(let [encode-fn (d/nilv encode-fn identity)
decode-fn (d/nilv decode-fn identity)
nitems (if (array? children)
(count (keep identity children))
1)
;; FIXME: we should handle this with CSS
width (mf/with-memo [nitems]
(if (= wide true)
"unset"
(fmt/format-pixels
(+ (* 4 (- nitems 1))
(* 32 nitems)))))
on-change'
(mf/use-fn
(mf/deps selected on-change)
(fn [event]
(let [input (dom/get-target event)
value (dom/get-target-val event)
;; Only allow null values when the "allow-empty" prop is true
value (when (or (not allow-empty)
(not= value selected)) value)]
(when (fn? on-change)
(on-change (decode-fn value) event))
(dom/blur! input))))
context-value
(mf/spread-object props
;; We pass a special metadata for disable
;; key casing transformation in this
;; concrete case, because this component
;; uses legacy mode and props are in
;; kebab-case style
^{::mf/transform false}
{:on-change on-change'
:encode-fn encode-fn
:decode-fn decode-fn})]
[:& (mf/provider context) {:value context-value}
[:div {:class (dm/str class " " (stl/css :radio-btn-wrapper))
:style {:width width}
:key (dm/str name "-" selected)}
children]]))

View File

@ -1,79 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
.radio-btn-wrapper {
@include deprecated.flexCenter;
border-radius: deprecated.$br-8;
height: deprecated.$s-32;
background-color: var(--input-background-color);
gap: deprecated.$s-4;
}
.radio-icon {
--radio-icon-border-color: var(--radio-btn-border-color);
@include deprecated.buttonStyle;
@include deprecated.flexCenter;
@include deprecated.focusRadio;
height: deprecated.$s-32;
flex-grow: 1;
border-radius: deprecated.$s-8;
box-sizing: border-box;
border: deprecated.$br-2 solid var(--radio-icon-border-color);
input {
display: none;
}
svg {
@extend .button-icon;
stroke: var(--radio-btn-foreground-color);
}
.title-name {
@include deprecated.uppercaseTitleTipography;
color: var(--radio-btn-foreground-color);
}
&:hover {
svg {
stroke: var(--radio-btn-foreground-color-selected);
}
}
}
.checked {
--radio-icon-border-color: var(--radio-btn-border-color-selected);
background-color: var(--radio-btn-background-color-selected);
svg {
stroke: var(--radio-btn-foreground-color-selected);
}
.title-name {
color: var(--radio-btn-foreground-color-selected);
}
}
.disabled {
cursor: default;
background-color: transparent;
border: deprecated.$s-2 solid transparent;
svg {
stroke: var(--button-foreground-color-disabled);
}
.title-name {
color: var(--button-foreground-color-disabled);
}
&:hover {
background-color: transparent;
border: deprecated.$s-2 solid transparent;
svg {
stroke: var(--button-foreground-color-disabled);
}
.title-name {
color: var(--button-foreground-color-disabled);
}
}
}

View File

@ -53,6 +53,6 @@
(mf/defc inspect-title-bar* (mf/defc inspect-title-bar*
[{:keys [class title]}] [{:keys [class title title-class]}]
[:div {:class [(stl/css :title-bar) class]} [:div {:class [(stl/css :title-bar) class]}
[:div {:class (stl/css :title-only :inspect-title)} title]]) [:div {:class [title-class (stl/css :title-only :inspect-title)]} title]])

View File

@ -51,10 +51,6 @@
padding: var(--sp-xxl) var(--sp-xxl) var(--sp-s) var(--sp-xxl); padding: var(--sp-xxl) var(--sp-xxl) var(--sp-s) var(--sp-xxl);
position: sticky; position: sticky;
top: 0; top: 0;
// We need to use the the deprecated z-index so it won't clash with the dashboard
// onboarding modals
z-index: deprecated.$z-index-3;
} }
.nav-inside { .nav-inside {

View File

@ -17,7 +17,7 @@
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.context-menu-a11y :refer [context-menu*]] [app.main.ui.components.context-menu-a11y :refer [context-menu*]]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader*]]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]] [app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.main.ui.notifications.context-notification :refer [context-notification]] [app.main.ui.notifications.context-notification :refer [context-notification]]
@ -184,7 +184,7 @@
:on-click on-click :on-click on-click
:tab-index "0"} :tab-index "0"}
[:span (tr "labels.add-custom-font")] [:span (tr "labels.add-custom-font")]
[:& file-uploader {:input-id "font-upload" [:> file-uploader* {:input-id "font-upload"
:accept accept-font-types :accept accept-font-types
:multi true :multi true
:ref input-ref :ref input-ref

View File

@ -16,7 +16,7 @@
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.errors :as errors] [app.main.errors :as errors]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader*]]
[app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.ds.product.loader :refer [loader*]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.main.ui.notifications.context-notification :refer [context-notification]] [app.main.ui.notifications.context-notification :refer [context-notification]]
@ -58,7 +58,7 @@
[{:keys [project-id on-finish-import]} external-ref] [{:keys [project-id on-finish-import]} external-ref]
(let [on-file-selected (use-import-file project-id on-finish-import)] (let [on-file-selected (use-import-file project-id on-finish-import)]
[:form.import-file {:aria-hidden "true"} [:form.import-file {:aria-hidden "true"}
[:& file-uploader {:accept ".penpot,.zip" [:> file-uploader* {:accept ".penpot,.zip"
:multi true :multi true
:ref external-ref :ref external-ref
:on-selected on-file-selected}]])) :on-selected on-file-selected}]]))

View File

@ -19,7 +19,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader*]]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.main.ui.dashboard.change-owner] [app.main.ui.dashboard.change-owner]
[app.main.ui.dashboard.subscription :refer [members-cta* [app.main.ui.dashboard.subscription :refer [members-cta*
@ -1315,7 +1315,7 @@
[:img {:class (stl/css :team-image) [:img {:class (stl/css :team-image)
:src (cfg/resolve-team-photo-url team)}] :src (cfg/resolve-team-photo-url team)}]
(when can-edit (when can-edit
[:& file-uploader {:accept "image/jpeg,image/png" [:> file-uploader* {:accept "image/jpeg,image/png"
:multi false :multi false
:ref finput :ref finput
:on-selected on-file-selected}])] :on-selected on-file-selected}])]

View File

@ -11,8 +11,10 @@
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]] [app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as k] [app.util.keyboard :as k]
@ -97,8 +99,11 @@
[:div {:class (stl/css :modal-container)} [:div {:class (stl/css :modal-container)}
[:div {:class (stl/css :modal-header)} [:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-title)} title] [:h2 {:class (stl/css :modal-title)} title]
[:button {:class (stl/css :modal-close-btn) [:> icon-button* {:variant "ghost"
:on-click cancel-fn} deprecated-icon/close]] :class (stl/css :modal-close-btn)
:icon i/close
:aria-label (tr "labels.close")
:on-click cancel-fn}]]
[:div {:class (stl/css :modal-content)} [:div {:class (stl/css :modal-content)}
(when (and (string? subtitle) (not= subtitle "")) (when (and (string? subtitle) (not= subtitle ""))
@ -124,14 +129,10 @@
[:div {:class (stl/css :modal-footer)} [:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)} [:div {:class (stl/css :action-buttons)}
(when-not (= cancel-label :omit) (when-not (= cancel-label :omit)
[:input {:class (stl/css :cancel-button) [:> button* {:variant "secondary"
:type "button" :on-click cancel-fn}
:value cancel-label cancel-label])
:on-click cancel-fn}])
[:input {:class (stl/css-case :accept-btn true [:> button* {:variant (if (= accept-style :danger) "destructive" "primary")
:danger (= accept-style :danger) :on-click accept-fn}
:primary (= accept-style :primary)) accept-label]]]]]))
:type "button"
:value accept-label
:on-click accept-fn}]]]]]))

View File

@ -33,7 +33,9 @@
} }
.modal-close-btn { .modal-close-btn {
@extend .modal-close-btn-base; position: absolute;
top: var(--sp-s);
right: var(--sp-s);
} }
.modal-content { .modal-content {
@ -53,17 +55,6 @@
@extend .modal-action-btns; @extend .modal-action-btns;
} }
.cancel-button {
@extend .modal-cancel-btn;
}
.accept-btn {
@extend .modal-accept-btn;
&.danger {
@extend .modal-danger-btn;
}
}
.modal-scd-msg { .modal-scd-msg {
margin-block: 0; margin-block: 0;
} }

View File

@ -12,6 +12,7 @@
[app.main.ui.ds.controls.combobox :refer [combobox*]] [app.main.ui.ds.controls.combobox :refer [combobox*]]
[app.main.ui.ds.controls.input :refer [input*]] [app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]] [app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.ds.controls.select :refer [select*]] [app.main.ui.ds.controls.select :refer [select*]]
[app.main.ui.ds.controls.switch :refer [switch*]] [app.main.ui.ds.controls.switch :refer [switch*]]
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]] [app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
@ -63,6 +64,7 @@
:Select select* :Select select*
:Switch switch* :Switch switch*
:Checkbox checkbox* :Checkbox checkbox*
:RadioButtons radio-buttons*
:Combobox combobox* :Combobox combobox*
:Text text* :Text text*
:TabSwitcher tab-switcher* :TabSwitcher tab-switcher*

View File

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

View File

@ -0,0 +1,107 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.controls.radio-buttons
(:require-macros
[app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon-list]]
[app.util.dom :as dom]
[rumext.v2 :as mf]
[rumext.v2.util :as mfu]))
(def ^:private schema:radio-button
[:map
[:id :string]
[:icon {:optional true}
[:and :string [:fn #(contains? icon-list %)]]]
[:label :string]
[:value [:or :keyword :string]]
[:disabled {:optional true} :boolean]])
(def ^:private schema:radio-buttons
[:map
[:class {:optional true} :string]
[:variant {:optional true}
[:maybe [:enum "primary" "secondary" "ghost" "destructive" "action"]]]
[:extended {:optional true} :boolean]
[:name {:optional true} :string]
[:selected {:optional true}
[:maybe [:or :keyword :string]]]
[:allow-empty {:optional true} :boolean]
[:options [:vector {:min 1} schema:radio-button]]
[:on-change {:optional true} fn?]])
(mf/defc radio-buttons*
{::mf/schema schema:radio-buttons}
[{:keys [class variant extended name selected allow-empty options on-change] :rest props}]
(let [options (if (array? options)
(mfu/bean options)
options)
type (if allow-empty "checkbox" "radio")
variant (d/nilv variant "secondary")
handle-click
(mf/use-fn
(fn [event]
(let [target (dom/get-target event)
label (dom/get-parent-with-data target "label")]
(dom/prevent-default event)
(dom/stop-propagation event)
(dom/click label))))
handle-change
(mf/use-fn
(mf/deps selected on-change)
(fn [event]
(let [input (dom/get-target event)
value (dom/get-target-val event)]
(when (fn? on-change)
(on-change value event))
(dom/blur! input))))
props
(mf/spread-props props {:key (dm/str name "-" selected)
:class [class (stl/css-case :wrapper true
:extended extended)]})]
[:> :div props
(for [[idx {:keys [id class value label icon disabled]}] (d/enumerate options)]
(let [checked? (= selected value)]
[:label {:key idx
:html-for id
:data-label true
:data-testid id
:class [class (stl/css-case :label true
:extended extended)]}
(if (some? icon)
[:> icon-button* {:variant variant
:on-click handle-click
:aria-pressed checked?
:aria-label label
:icon icon
:disabled disabled}]
[:> button* {:variant variant
:on-click handle-click
:aria-pressed checked?
:class (stl/css-case :button true
:extended extended)
:disabled disabled}
label])
[:input {:id id
:class (stl/css :input)
:on-change handle-change
:type type
:name name
:disabled disabled
:value value
:default-checked checked?}]]))]))

View File

@ -0,0 +1,97 @@
{ /* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) KALEIDOS INC */ }
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as RadioButtons from "./radio_buttons.stories";
<Meta title="Controls/Radio Buttons" />
# Radio Buttons
The `radio-buttons*` component allows users to switch between two or more options that are mutually exclusive.
## Variants
Radio buttons with text only. The label will be the text of the button.
<Canvas of={RadioButtons.Default} />
```clj
[:> radio-buttons* {:selected "left"
:on-change handle-change
:name "alignment"
:extended false
:allow-empty false
:options [{:id "align-left"
:label "Left"
:value "left"}
{:id "align-center"
:label "Center"
:value "center"}
{:id "align-right"
:label "Right"
:value "right"}]}]
```
Radio buttons with icons only. In this case, the label will act as the tooltip of each button.
<Canvas of={RadioButtons.WithIcons} />
```clj
(ns app.main.ui.foo
(:require
[app.main.ui.ds.foundations.assets.icon :as i]))
[:> radio-buttons* {:selected "left"
:on-change handle-change
:name "alignment"
:extended false
:allow-empty false
:options [{:id "align-left"
:icon i/text-align-left
:label "Left align"
:value "left"}
{:id "align-center"
:icon i/text-align-center
:label "Center align"
:value "center"}
{:id "align-right"
:icon i/text-align-right
:label "Right align"
:value "right"}]}]
```
## Anatomy
Under the hood, each option is represented by
- a button, which is the visible and clickable element. It may be either an icon button or a text button.
- a radio input, which is not visible but retains the current state of the option.
A radio group is defined by giving each of radio buttons in the group the same name. Once a radio group is established,
selecting any radio button in that group automatically deselects any currently-selected radio button in the same group.
The `selected` parameter should be set to the value of the option that is to be active. Otherwise, no option will be selected.
If the parameter `allow-empty` is enabled, then the component will work with checkboxes instead of radio buttons,
and therefore the selected option can be deselected. However, it will still only be possible to select one option.
The `extended` parameter allows the component to use all the available space from the parent and distribute it equally
among all elements.
Any option can be individually disabled using the `disabled` parameter.
## Usage Guidelines
### When to Use
- For multiple choice settings that take effect immediately.
- In preference panels and configuration screens.
### When Not to Use
- For boolean settings (use switch or checkbox instead).
- For actions that require confirmation (use buttons instead).
- For temporary states that need explicit "Apply" action.

View File

@ -0,0 +1,40 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
.wrapper {
display: flex;
justify-content: flex-start;
align-items: center;
width: fit-content;
border-radius: $br-8;
background-color: var(--color-background-tertiary);
gap: var(--sp-xs);
&.extended {
width: 100%;
display: flex;
}
}
.label {
&.extended {
display: flex;
flex: 1 1 0;
}
}
.button {
&.extended {
flex-grow: 1;
}
}
.input {
display: none;
}

View File

@ -0,0 +1,72 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
import * as React from "react";
import Components from "@target/components";
const { RadioButtons } = Components;
const options = [
{id: "left", label: "Left", value: "left" },
{id: "center", label: "Center", value: "center" },
{id: "right", label: "Right", value: "right" },
];
const optionsIcon = [
{id: "left", label: "Left align", value: "left", icon: "text-align-left" },
{id: "center", label: "Center align", value: "center", icon: "text-align-center" },
{id: "right", label: "Right align", value: "right", icon: "text-align-right" },
];
export default {
title: "Controls/Radio Buttons",
component: RadioButtons,
argTypes: {
name: {
control: { type: "text" },
description: "Whether the checkbox is checked",
},
selected: {
control: { type: "select" },
options: ["", "left", "center", "right"],
description: "Whether the checkbox is checked",
},
extended: {
control: { type: "boolean" },
description: "Whether the checkbox is checked",
},
allowEmpty: {
control: { type: "boolean" },
description: "Whether the checkbox is checked",
},
disabled: {
control: { type: "boolean" },
description: "Whether the checkbox is disabled",
},
},
args: {
name: "alignment",
selected: "left",
extended: false,
allowEmpty: false,
options: options,
disabled: false,
},
parameters: {
controls: {
exclude: ["options", "on-change"],
},
},
render: ({ ...args }) => <RadioButtons {...args} />,
};
export const Default = {};
export const WithIcons = {
args: {
options: optionsIcon,
},
};

View File

@ -208,7 +208,7 @@
;; FIXME: deprecated, should be refactored in two components and use ;; FIXME: deprecated, should be refactored in two components and use
;; the generic progress reporter ;; the generic progress reporter
(mf/defc progress-widget (mf/defc progress-widget*
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[] []
(let [state (mf/deref refs/export) (let [state (mf/deref refs/export)

View File

@ -121,7 +121,7 @@
[:div {:class (stl/css :modal-container)} [:div {:class (stl/css :modal-container)}
[:div {:class (stl/css :modal-header)} [:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-title)} [:h2 {:class (stl/css :modal-title)}
(tr "dashboard.export.title")] (tr "files-download-modal.title")]
[:button {:class (stl/css :modal-close-btn) [:button {:class (stl/css :modal-close-btn)
:on-click on-cancel} deprecated-icon/close]] :on-click on-cancel} deprecated-icon/close]]
@ -129,8 +129,8 @@
(= status :prepare) (= status :prepare)
[:* [:*
[:div {:class (stl/css :modal-content)} [:div {:class (stl/css :modal-content)}
[:p {:class (stl/css :modal-msg)} (tr "dashboard.export.explain")] [:p {:class (stl/css :modal-msg)} (tr "files-download-modal.description-1")]
[:p {:class (stl/css :modal-scd-msg)} (tr "dashboard.export.detail")] [:p {:class (stl/css :modal-scd-msg)} (tr "files-download-modal.description-2")]
(for [type fexp/valid-types] (for [type fexp/valid-types]
[:div {:class (stl/css :export-option true) [:div {:class (stl/css :export-option true)
@ -138,20 +138,20 @@
[:label {:for (str "export-" type) [:label {:for (str "export-" type)
:class (stl/css-case :global/checked (= selected type))} :class (stl/css-case :global/checked (= selected type))}
;; Execution time translation strings: ;; Execution time translation strings:
;; (tr "dashboard.export.options.all.message") ;; (tr "files-download-modal.options.all.message")
;; (tr "dashboard.export.options.all.title") ;; (tr "files-download-modal.options.all.title")
;; (tr "dashboard.export.options.detach.message") ;; (tr "files-download-modal.options.detach.message")
;; (tr "dashboard.export.options.detach.title") ;; (tr "files-download-modal.options.detach.title")
;; (tr "dashboard.export.options.merge.message") ;; (tr "files-download-modal.options.merge.message")
;; (tr "dashboard.export.options.merge.title") ;; (tr "files-download-modal.options.merge.title")
[:span {:class (stl/css-case :global/checked (= selected type))} [:span {:class (stl/css-case :global/checked (= selected type))}
(when (= selected type) (when (= selected type)
deprecated-icon/status-tick)] deprecated-icon/status-tick)]
[:div {:class (stl/css :option-content)} [:div {:class (stl/css :option-content)}
[:h3 {:class (stl/css :modal-subtitle)} [:h3 {:class (stl/css :modal-subtitle)}
(tr (dm/str "dashboard.export.options." (d/name type) ".title"))] (tr (dm/str "files-download-modal.options." (d/name type) ".title"))]
[:p {:class (stl/css :modal-msg)} [:p {:class (stl/css :modal-msg)}
(tr (dm/str "dashboard.export.options." (d/name type) ".message"))]] (tr (dm/str "files-download-modal.options." (d/name type) ".message"))]]
[:input {:type "radio" [:input {:type "radio"
:class (stl/css :option-input) :class (stl/css :option-input)

View File

@ -12,14 +12,17 @@
flex-direction: column; flex-direction: column;
gap: var(--sp-l); gap: var(--sp-l);
width: 100%; width: 100%;
height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value max-height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
padding-top: var(--sp-s); padding-top: var(--sp-s);
padding-inline: var(--sp-m);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
scrollbar-gutter: stable; scrollbar-gutter: stable;
background-color: var(--low-emphasis-background);
} }
.workspace-element-options { .workspace-element-options {
height: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value max-height: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
padding-inline: var(--sp-m); padding-inline: var(--sp-m);
background-color: var(--low-emphasis-background);
} }

View File

@ -24,7 +24,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "inspect.attributes.blur") {:title (tr "inspect.attributes.blur")
:class (stl/css :title-spacing-blur)} :class (stl/css :title-wrapper)
:title-class (stl/css :blur-attr-title)}
(when (= (count shapes) 1) (when (= (count shapes) 1)
[:> copy-button* {:data (css/get-css-property objects (first shapes) :filter) [:> copy-button* {:data (css/get-css-property objects (first shapes) :filter)
:class (stl/css :copy-btn-title)}])] :class (stl/css :copy-btn-title)}])]

View File

@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-blur { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.blur-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.blur-row { .blur-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -23,5 +34,5 @@
} }
.copy-btn-title { .copy-btn-title {
max-width: deprecated.$s-28; max-inline-size: $sz-28;
} }

View File

@ -68,7 +68,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "inspect.attributes.fill") {:title (tr "inspect.attributes.fill")
:class (stl/css :title-spacing-fill)}] :class (stl/css :title-wrapper)
:class-title (stl/css :fill-attr-title)}]
[:div {:class (stl/css :attributes-content)} [:div {:class (stl/css :attributes-content)}
(for [shape shapes] (for [shape shapes]

View File

@ -5,16 +5,30 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-fill { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.fill-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.attributes-content { .attributes-content {
display: grid; display: grid;
gap: deprecated.$s-4; gap: deprecated.$s-4;
} }
.attributes-fill-block {
block-size: $sz-36;
}

View File

@ -44,7 +44,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "inspect.attributes.size") {:title (tr "inspect.attributes.size")
:class (stl/css :title-spacing-geometry)} :class (stl/css :title-wrapper)
:title-class (stl/css :geometry-attr-title)}
(when (= (count shapes) 1) (when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)

View File

@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-geometry { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.geometry-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.geometry-row { .geometry-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -23,5 +34,5 @@
} }
.copy-btn-title { .copy-btn-title {
max-width: deprecated.$s-28; max-inline-size: $sz-28;
} }

View File

@ -57,7 +57,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title "Layout" {:title "Layout"
:class (stl/css :title-spacing-layout)} :class (stl/css :title-wrapper)
:title-class (stl/css :layout-attr-title)}
(when (= (count shapes) 1) (when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)

View File

@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-layout { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.layout-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.layout-row { .layout-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -23,5 +34,5 @@
} }
.copy-btn-title { .copy-btn-title {
max-width: deprecated.$s-28; max-inline-size: $sz-28;
} }

View File

@ -69,7 +69,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> title-bar* {:collapsable false [:> title-bar* {:collapsable false
:title menu-title :title menu-title
:class (stl/css :title-spacing-layout-element)} :class (stl/css :title-wrapper)
:title-class (stl/css :layout-element-attr-title)}
(when (= (count shapes) 1) (when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
:class (stl/css :copy-btn-title)}])] :class (stl/css :copy-btn-title)}])]

View File

@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-layout-element { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.layout-element-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.layout-element-row { .layout-element-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -23,5 +34,6 @@
} }
.copy-btn-title { .copy-btn-title {
max-width: deprecated.$s-28; max-inline-size: $sz-28;
max-inline-size: $sz-28;
} }

View File

@ -63,7 +63,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "inspect.attributes.shadow") {:title (tr "inspect.attributes.shadow")
:class (stl/css :title-spacing-shadow)}] :class (stl/css :title-wrapper)
:title-class (stl/css :shadow-attr-title)}]
[:div {:class (stl/css :attributes-content)} [:div {:class (stl/css :attributes-content)}
(for [shape shapes] (for [shape shapes]

View File

@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-shadow { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.shadow-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.shadow-row { .shadow-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {

View File

@ -88,7 +88,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "inspect.attributes.stroke") {:title (tr "inspect.attributes.stroke")
:class (stl/css :title-spacing-stroke)}] :class (stl/css :title-wrapper)
:title-class (stl/css :stroke-attr-title)}]
[:div {:class (stl/css :attributes-content)} [:div {:class (stl/css :attributes-content)}
(for [shape shapes] (for [shape shapes]

View File

@ -5,21 +5,34 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-stroke { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.stroke-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.attributes-stroke-block { .attributes-stroke-block {
@include deprecated.flexColumn; display: flex;
flex-direction: column;
gap: var(--sp-xs);
} }
.stroke-row { .stroke-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -28,5 +41,5 @@
.attributes-content { .attributes-content {
display: grid; display: grid;
gap: deprecated.$s-4; gap: var(--sp-xs);
} }

View File

@ -54,5 +54,6 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "workspace.sidebar.options.svg-attrs.title") {:title (tr "workspace.sidebar.options.svg-attrs.title")
:class (stl/css :title-spacing-svg)}] :class (stl/css :title-wrapper)
:title-class (stl/css :svg-attr-title)}]
[:& svg-block {:shape shape}]]))) [:& svg-block {:shape shape}]])))

View File

@ -5,17 +5,29 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/typography.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-svg { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.svg-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.svg-row { .svg-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -23,12 +35,12 @@
} }
.attributes-subtitle { .attributes-subtitle {
@include deprecated.uppercaseTitleTipography; @include use-typography("headline-small");
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
height: deprecated.$s-32; block-size: $sz-32;
span { span {
height: deprecated.$s-32; block-size: $sz-32;
display: flex; display: flex;
align-items: center; align-items: center;
} }

View File

@ -157,7 +157,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (tr "inspect.attributes.typography") {:title (tr "inspect.attributes.typography")
:class (stl/css :title-spacing-text)}] :class (stl/css :title-wrapper)
:title-class (stl/css :text-atrr-title)}]
(for [shape shapes] (for [shape shapes]
[:& text-block {:shape shape [:& text-block {:shape shape

View File

@ -5,23 +5,37 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/typography.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-text { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.text-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.attributes-content { .attributes-content {
@include deprecated.flexColumn; display: flex;
flex-direction: column;
gap: var(--sp-xs);
} }
.text-row { .text-row {
@extend .attr-row; @extend .attr-row;
height: unset; block-size: unset;
min-height: deprecated.$s-32; min-block-size: $sz-36;
:global(.attr-value) { :global(.attr-value) {
align-items: center; align-items: center;
} }
@ -32,20 +46,20 @@
} }
.attributes-content-row { .attributes-content-row {
max-width: deprecated.$s-240; max-inline-size: px2rem(240);
min-height: calc(deprecated.$s-2 + deprecated.$s-32); min-block-size: px2rem(34);
border-radius: deprecated.$br-8; border-radius: $br-8;
border: deprecated.$s-1 solid var(--menu-border-color-disabled); border: $b-1 solid var(--menu-border-color-disabled);
margin-top: deprecated.$s-4; margin-block-start: var(--sp-xs);
.content { .content {
@include deprecated.bodySmallTypography; @include use-typography("body-small");
width: 100%; width: 100%;
padding: deprecated.$s-4 0; padding: var(--sp-xs) 0;
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
} }
&:hover { &:hover {
border: deprecated.$s-1 solid var(--color-background-tertiary); border: $b-1 solid var(--color-background-tertiary);
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
.content { .content {
color: var(--menu-foreground-color-hover); color: var(--menu-foreground-color-hover);

View File

@ -42,7 +42,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title (if is-container? (tr "inspect.attributes.variants") (tr "inspect.attributes.variant")) {:title (if is-container? (tr "inspect.attributes.variants") (tr "inspect.attributes.variant"))
:class (stl/css :title-spacing-variant)}] :class (stl/css :title-wrapper)
:title-class (stl/css :variant-attr-title)}]
(for [[pos property] (map-indexed vector properties)] (for [[pos property] (map-indexed vector properties)]
[:> variant-block* {:key (dm/str "variant-property-" pos) :name (:name property) :value (:value property)}])])) [:> variant-block* {:key (dm/str "variant-property-" pos) :name (:name property) :value (:value property)}])]))

View File

@ -5,18 +5,29 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-variant { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.variant-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.variant-row { .variant-row {
@extend .attr-row; @extend .attr-row;
height: fit-content; block-size: fit-content;
min-block-size: $sz-36;
} }
.button-children { .button-children {

View File

@ -51,7 +51,8 @@
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar* [:> inspect-title-bar*
{:title "Visibility" {:title "Visibility"
:class (stl/css :title-spacing-visibility)} :class (stl/css :title-wrapper)
:title-class (stl/css :visibility-attr-title)}
(when (= (count shapes) 1) (when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties) [:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)

View File

@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC // Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated; @use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block { .attributes-block {
@include deprecated.flexColumn; --box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
} }
.title-spacing-visibility { .title-wrapper {
@extend .attr-title; margin-inline-start: 0;
}
.visibility-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
} }
.visibility-row { .visibility-row {
@extend .attr-row; @extend .attr-row;
block-size: $sz-36;
} }
.button-children { .button-children {
@ -23,5 +34,5 @@
} }
.copy-btn-title { .copy-btn-title {
max-width: deprecated.$s-28; max-inline-size: $sz-28;
} }

View File

@ -19,7 +19,10 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.code-block :refer [code-block]] [app.main.ui.components.code-block :refer [code-block]]
[app.main.ui.components.copy-button :refer [copy-button*]] [app.main.ui.components.copy-button :refer [copy-button*]]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.hooks.resize :refer [use-resize-hook]] [app.main.ui.hooks.resize :refer [use-resize-hook]]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.main.ui.shapes.text.fontfaces :refer [shapes->fonts]] [app.main.ui.shapes.text.fontfaces :refer [shapes->fonts]]
@ -260,7 +263,8 @@
[:div {:class (stl/css-case :element-options true [:div {:class (stl/css-case :element-options true
:viewer-code-block (= :viewer from))} :viewer-code-block (= :viewer from))}
[:div {:class (stl/css :attributes-block)} [:div {:class (stl/css :attributes-block)}
[:button {:class (stl/css :download-button) [:> button* {:variant "secondary"
:class (stl/css :download-button)
:on-click handle-copy-all-code} :on-click handle-copy-all-code}
"Copy all code"]] "Copy all code"]]
@ -288,10 +292,10 @@
;; :options [{:label "CSS" :value "css"}]}] ;; :options [{:label "CSS" :value "css"}]}]
[:div {:class (stl/css :action-btns)} [:div {:class (stl/css :action-btns)}
[:button {:class (stl/css :expand-button) [:> icon-button* {:variant "ghost"
:on-click on-expand} :aria-label "Expand"
deprecated-icon/code] :on-click on-expand
:icon i/code}]
[:> copy-button* {:data copy-css-fn [:> copy-button* {:data copy-css-fn
:class (stl/css :css-copy-btn) :class (stl/css :css-copy-btn)
:on-copied on-style-copied}]]] :on-copied on-style-copied}]]]
@ -318,21 +322,21 @@
:rotated collapsed-markup?)} :rotated collapsed-markup?)}
deprecated-icon/arrow]] deprecated-icon/arrow]]
[:& radio-buttons {:selected markup-type [:> radio-buttons* {:selected markup-type
:on-change set-markup :on-change set-markup
:class (stl/css :code-lang-options) :name "listing-style"
:wide true :options [{:id "html"
:name "listing-style"} :label "HTML"
[:& radio-button {:value "html" :value "html"}
:id :html}] {:id "svg"
[:& radio-button {:value "svg" :label "SVG"
:id :svg}]] :value "svg"}]}]
[:div {:class (stl/css :action-btns)} [:div {:class (stl/css :action-btns)}
[:button {:class (stl/css :expand-button) [:> icon-button* {:variant "ghost"
:on-click on-expand} :aria-label "Expand"
deprecated-icon/code] :on-click on-expand
:icon i/code}]
[:> copy-button* {:data copy-html-fn [:> copy-button* {:data copy-html-fn
:class (stl/css :html-copy-btn) :class (stl/css :html-copy-btn)
:on-copied on-markup-copied}]]] :on-copied on-markup-copied}]]]

View File

@ -17,16 +17,18 @@
padding-inline: var(--sp-m); padding-inline: var(--sp-m);
} }
.attributes-block {
display: flex;
flex-direction: column;
row-gap: 12px;
}
.viewer-code-block { .viewer-code-block {
height: calc(100vh - #{deprecated.$s-108}); // TODO: Fix this hardcoded value height: calc(100vh - #{deprecated.$s-108}); // TODO: Fix this hardcoded value
} }
.download-button { .download-button {
@extend .button-secondary; margin: var(--sp-s) 0;
@include deprecated.uppercaseTitleTipography;
height: deprecated.$s-32;
width: 100%;
margin: deprecated.$s-8 0;
} }
.code-block { .code-block {
@ -73,7 +75,6 @@
gap: deprecated.$s-4; gap: deprecated.$s-4;
} }
.expand-button,
.css-copy-btn, .css-copy-btn,
.html-copy-btn { .html-copy-btn {
@extend .button-tertiary; @extend .button-tertiary;
@ -85,9 +86,6 @@
} }
} }
.code-lang-options {
max-width: deprecated.$s-108;
}
.code-lang-select { .code-lang-select {
@include deprecated.uppercaseTitleTipography; @include deprecated.uppercaseTitleTipography;
width: deprecated.$s-72; width: deprecated.$s-72;

View File

@ -186,6 +186,7 @@
[:> styles-tab* {:color-space color-space [:> styles-tab* {:color-space color-space
:objects objects :objects objects
:shapes shapes :shapes shapes
:from from
:libraries libraries :libraries libraries
:file-id file-id}] :file-id file-id}]
:computed :computed

View File

@ -24,10 +24,6 @@
} }
} }
.viewer-code {
padding-inline-start: var(--sp-s);
}
.tool-windows { .tool-windows {
block-size: 100%; block-size: 100%;
display: grid; display: grid;

View File

@ -90,7 +90,7 @@
:multiple)) :multiple))
(mf/defc styles-tab* (mf/defc styles-tab*
[{:keys [color-space shapes libraries objects file-id]}] [{:keys [color-space shapes libraries objects file-id from]}]
(let [data (dm/get-in libraries [file-id :data]) (let [data (dm/get-in libraries [file-id :data])
first-shape (first shapes) first-shape (first shapes)
first-component (ctkl/get-component data (:component-id first-shape)) first-component (ctkl/get-component data (:component-id first-shape))
@ -131,7 +131,8 @@
(mf/deps shorthands*) (mf/deps shorthands*)
(fn [shorthand] (fn [shorthand]
(swap! shorthands* assoc (:panel shorthand) (:property shorthand))))] (swap! shorthands* assoc (:panel shorthand) (:property shorthand))))]
[:ol {:class (stl/css :styles-tab) :aria-label (tr "labels.styles")} [:ol {:class (stl/css-case :styles-tab true
:styles-tab-workspace (= from :workspace)) :aria-label (tr "labels.styles")}
;; TOKENS PANEL ;; TOKENS PANEL
(when (or (seq active-themes) (seq active-sets)) (when (or (seq active-themes) (seq active-sets))
[:li [:li

View File

@ -7,5 +7,9 @@
@use "ds/_utils.scss" as *; @use "ds/_utils.scss" as *;
.styles-tab { .styles-tab {
block-size: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value block-size: calc(100vh - px2rem(140)); // TODO: Fix this hardcoded value
}
.styles-tab-workspace {
block-size: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
} }

View File

@ -60,6 +60,7 @@
} }
.property-detail-text { .property-detail-text {
@include use-typography("body-small");
color: var(--detail-color); color: var(--detail-color);
} }

View File

@ -19,7 +19,7 @@
(case type (case type
:variant (tr "inspect.tabs.styles.variants-panel") :variant (tr "inspect.tabs.styles.variants-panel")
:token (tr "inspect.tabs.styles.token-panel") :token (tr "inspect.tabs.styles.token-panel")
:geometry (tr "inspect.tabs.styles.geometry-panel") :geometry (tr "inspect.attributes.size")
:fill (tr "labels.fill") :fill (tr "labels.fill")
:stroke (tr "labels.stroke") :stroke (tr "labels.stroke")
:text (tr "labels.text") :text (tr "labels.text")

View File

@ -33,7 +33,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--title-gap); gap: var(--title-gap);
padding-block: var(--title-padding);
} }
.disclosure-button { .disclosure-button {
@ -52,4 +51,5 @@
@include use-typography("headline-small"); @include use-typography("headline-small");
flex: 1; flex: 1;
color: var(--title-color); color: var(--title-color);
padding-block: var(--title-padding);
} }

View File

@ -14,7 +14,7 @@
[app.main.data.profile :as du] [app.main.data.profile :as du]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader*]]
[app.main.ui.components.forms :as fm] [app.main.ui.components.forms :as fm]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
@ -110,7 +110,7 @@
[:span {:class (stl/css :update-overlay) [:span {:class (stl/css :update-overlay)
:on-click on-image-click} (tr "labels.update")] :on-click on-image-click} (tr "labels.update")]
[:img {:src photo}] [:img {:src photo}]
[:& file-uploader {:accept "image/jpeg,image/png" [:> file-uploader* {:accept "image/jpeg,image/png"
:multi false :multi false
:ref input-ref :ref input-ref
:on-selected on-file-selected :on-selected on-file-selected

View File

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

View File

@ -14,10 +14,13 @@
[app.main.data.viewer.shortcuts :as sc] [app.main.data.viewer.shortcuts :as sc]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.exports.assets :refer [progress-widget]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.exports.assets :refer [progress-widget*]]
[app.main.ui.formats :as fmt] [app.main.ui.formats :as fmt]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.main.ui.viewer.comments :refer [comments-menu]] [app.main.ui.viewer.comments :refer [comments-menu*]]
[app.main.ui.viewer.interactions :refer [flows-menu* interactions-menu*]] [app.main.ui.viewer.interactions :refer [flows-menu* interactions-menu*]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
@ -33,20 +36,12 @@
[] []
(modal/show! :login-register {})) (modal/show! :login-register {}))
(mf/defc zoom-widget (mf/defc zoom-widget*
{::mf/memo true {::mf/memo true}
::mf/props :obj} [{:keys [zoom on-increase on-decrease on-zoom-reset on-fullscreen on-zoom-fit on-zoom-fill]}]
[{:keys [zoom
on-increase
on-decrease
on-zoom-reset
on-fullscreen
on-zoom-fit
on-zoom-fill]
:as props}]
(let [open* (mf/use-state false) (let [open* (mf/use-state false)
open? (deref open*) open? (deref open*)
open-dropdown open-dropdown
(mf/use-fn (mf/use-fn
(fn [event] (fn [event]
@ -75,7 +70,7 @@
[:div {:class (stl/css-case :zoom-widget true [:div {:class (stl/css-case :zoom-widget true
:selected open?) :selected open?)
:on-click open-dropdown :on-click (if open? close-dropdown open-dropdown)
:title (tr "workspace.header.zoom")} :title (tr "workspace.header.zoom")}
[:span {:class (stl/css :label)} (fmt/format-percent zoom)] [:span {:class (stl/css :label)} (fmt/format-percent zoom)]
[:& dropdown {:show open? [:& dropdown {:show open?
@ -83,17 +78,17 @@
[:ul {:class (stl/css :dropdown)} [:ul {:class (stl/css :dropdown)}
[:li {:class (stl/css :basic-zoom-bar)} [:li {:class (stl/css :basic-zoom-bar)}
[:span {:class (stl/css :zoom-btns)} [:span {:class (stl/css :zoom-btns)}
[:button {:class (stl/css :zoom-btn) [:> icon-button* {:variant "ghost"
:on-click on-decrease} :aria-label (tr "shortcuts.decrease-zoom")
[:span {:class (stl/css :zoom-icon)} :on-click on-decrease
deprecated-icon/remove-icon]] :icon i/remove}]
[:p {:class (stl/css :zoom-text)} [:p {:class (stl/css :zoom-text)}
(fmt/format-percent zoom)] (fmt/format-percent zoom)]
[:button {:class (stl/css :zoom-btn) [:> icon-button* {:variant "ghost"
:on-click on-increase} :aria-label (tr "shortcuts.increase-zoom")
[:span {:class (stl/css :zoom-icon)} :on-click on-increase
deprecated-icon/add]]] :icon i/add}]]
[:button {:class (stl/css :reset-btn) [:> button* {:variant "ghost"
:on-click on-zoom-reset} :on-click on-zoom-reset}
(tr "workspace.header.reset-zoom")]] (tr "workspace.header.reset-zoom")]]
@ -119,7 +114,7 @@
[:span {:class (stl/css :shortcut-key) [:span {:class (stl/css :shortcut-key)
:key (dm/str "zoom-fullscreen-" sc)} sc])]]]]])) :key (dm/str "zoom-fullscreen-" sc)} sc])]]]]]))
(mf/defc header-options (mf/defc header-options*
[{:keys [section zoom page file index permissions interactions-mode share]}] [{:keys [section zoom page file index permissions interactions-mode share]}]
(let [fullscreen? (mf/deref fullscreen-ref) (let [fullscreen? (mf/deref fullscreen-ref)
@ -159,6 +154,7 @@
handle-zoom-fit handle-zoom-fit
(mf/use-fn (mf/use-fn
#(st/emit! dv/zoom-to-fit))] #(st/emit! dv/zoom-to-fit))]
(mf/with-effect [permissions share] (mf/with-effect [permissions share]
(when (and (when (and
(:in-team permissions) (:in-team permissions)
@ -167,7 +163,7 @@
(open-share-dialog))) (open-share-dialog)))
[:div {:class (stl/css :options-zone)} [:div {:class (stl/css :options-zone)}
[:& progress-widget] [:> progress-widget*]
(case section (case section
:interactions [:* :interactions [:*
@ -175,11 +171,10 @@
[:> flows-menu* {:page page :index index}]) [:> flows-menu* {:page page :index index}])
[:> interactions-menu* [:> interactions-menu*
{:interactions-mode interactions-mode}]] {:interactions-mode interactions-mode}]]
:comments [:& comments-menu] :comments [:> comments-menu*]
[:div {:class (stl/css :view-options)}]) [:div {:class (stl/css :view-options)}])
[:& zoom-widget [:> zoom-widget* {:zoom zoom
{:zoom zoom
:on-increase handle-increase :on-increase handle-increase
:on-decrease handle-decrease :on-decrease handle-decrease
:on-zoom-reset handle-zoom-reset :on-zoom-reset handle-zoom-reset
@ -188,27 +183,29 @@
:on-fullscreen toggle-fullscreen}] :on-fullscreen toggle-fullscreen}]
(when (:in-team permissions) (when (:in-team permissions)
[:span {:on-click go-to-workspace [:> icon-button* {:variant "ghost"
:class (stl/css :edit-btn)} :aria-label (tr "viewer.header.edit-in-workspace")
deprecated-icon/curve]) :on-click go-to-workspace
:icon i/curve}])
[:span {:title (tr "viewer.header.fullscreen") [:> icon-button* {:variant "ghost"
:class (stl/css-case :fullscreen-btn true :aria-pressed fullscreen?
:selected fullscreen?) :aria-label (tr "viewer.header.fullscreen")
:on-click toggle-fullscreen} :on-click toggle-fullscreen
deprecated-icon/expand] :icon i/expand}]
(when (:in-team permissions) (when (:in-team permissions)
[:button {:on-click open-share-dialog [:> button* {:variant "primary"
:class (stl/css :share-btn)} :class (stl/css :share-btn)
:on-click open-share-dialog}
(tr "labels.share")]) (tr "labels.share")])
(when-not (:is-logged permissions) (when-not (:is-logged permissions)
[:span {:on-click open-login-dialog [:span {:on-click open-login-dialog
:class (stl/css :go-log-btn)} (tr "labels.log-or-sign")])])) :class (stl/css :go-log-btn)} (tr "labels.log-or-sign")])]))
(mf/defc header-sitemap (mf/defc header-sitemap*
[{:keys [project file page frame toggle-thumbnails] :as props}] [{:keys [project file page frame toggle-thumbnails]}]
(let [project-name (:name project) (let [project-name (:name project)
file-name (:name file) file-name (:name file)
page-name (:name page) page-name (:name page)
@ -317,7 +314,7 @@
:pointer-events (when-not (:in-team permissions) "none")}} :pointer-events (when-not (:in-team permissions) "none")}}
penpot-logo-icon] penpot-logo-icon]
[:& header-sitemap {:project project [:> header-sitemap* {:project project
:file file :file file
:page page :page page
:frame frame :frame frame
@ -325,32 +322,32 @@
:index index}]] :index index}]]
[:div {:class (stl/css :mode-zone)} [:div {:class (stl/css :mode-zone)}
[:button {:on-click navigate [:> icon-button* {:variant "ghost"
:aria-pressed (= section :interactions)
:aria-label (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))
:data-value "interactions" :data-value "interactions"
:class (stl/css-case :mode-zone-btn true :on-click navigate
:selected (= section :interactions)) :icon i/play}]
:title (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))}
deprecated-icon/play]
(when (or (:in-team permissions) (when (or (:in-team permissions)
(= (:who-comment permissions) "all")) (= (:who-comment permissions) "all"))
[:button {:on-click navigate [:> icon-button* {:variant "ghost"
:aria-pressed (= section :comments)
:aria-label (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))
:data-value "comments" :data-value "comments"
:class (stl/css-case :mode-zone-btn true :on-click navigate
:selected (= section :comments)) :icon i/comments}])
:title (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))}
deprecated-icon/comments])
(when (or (:in-team permissions) (when (or (:in-team permissions)
(and (= (:type permissions) :share-link) (and (= (:type permissions) :share-link)
(= (:who-inspect permissions) "all"))) (= (:who-inspect permissions) "all")))
[:button {:on-click go-to-inspect [:> icon-button* {:variant "ghost"
:class (stl/css-case :mode-zone-btn true :aria-pressed (= section :inspect)
:selected (= section :inspect)) :aria-label (tr "viewer.header.inspect-section" (sc/get-tooltip :open-inspect))
:title (tr "viewer.header.inspect-section" (sc/get-tooltip :open-inspect))} :on-click go-to-inspect
deprecated-icon/code])] :icon i/code}])]
[:& header-options {:section section [:> header-options* {:section section
:permissions permissions :permissions permissions
:page page :page page
:file file :file file

View File

@ -12,7 +12,7 @@
grid-column: 1 / span 1; grid-column: 1 / span 1;
grid-row: 1 / span 1; grid-row: 1 / span 1;
display: grid; display: grid;
grid-template-columns: 1fr deprecated.$s-92 1fr; grid-template-columns: 1fr auto 1fr;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: deprecated.$s-48; height: deprecated.$s-48;
@ -130,23 +130,9 @@
// SECTION BUTTONS // SECTION BUTTONS
.mode-zone { .mode-zone {
@include deprecated.flexRow; display: flex;
height: 100%; flex-direction: row;
} gap: var(--sp-xs);
.mode-zone-btn {
@extend .button-tertiary;
@include deprecated.flexCenter;
height: deprecated.$s-32;
width: deprecated.$s-28;
padding: 0;
svg {
@extend .button-icon;
}
}
.selected {
@extend .button-icon-selected;
} }
// OPTION AREA // OPTION AREA
@ -165,33 +151,8 @@
cursor: pointer; cursor: pointer;
} }
.fullscreen-btn {
@extend .button-tertiary;
@include deprecated.flexCenter;
height: deprecated.$s-32;
width: deprecated.$s-28;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
}
}
.share-btn { .share-btn {
@extend .button-primary; margin-left: var(--sp-xs);
height: deprecated.$s-32;
min-width: deprecated.$s-72;
margin-left: deprecated.$s-4;
}
.edit-btn {
@extend .button-tertiary;
@include deprecated.flexCenter;
height: deprecated.$s-32;
width: deprecated.$s-28;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
}
} }
.go-log-btn { .go-log-btn {
@ -245,43 +206,15 @@
display: flex; display: flex;
} }
.zoom-btn {
@extend .button-tertiary;
height: deprecated.$s-28;
width: deprecated.$s-28;
border-radius: deprecated.$br-8;
.zoom-icon {
@include deprecated.flexCenter;
width: deprecated.$s-24;
height: deprecated.$s-32;
svg {
@extend .button-icon;
stroke: var(--icon-foreground);
}
}
&:hover {
.zoom-icon svg {
stroke: var(--button-tertiary-foreground-color-hover);
}
}
}
.zoom-text { .zoom-text {
@include deprecated.flexCenter; @include deprecated.flexCenter;
height: 100%; height: 100%;
min-width: deprecated.$s-64; min-width: deprecated.$s-48;
padding: 0; padding: 0;
margin: 0 deprecated.$s-2; margin: 0 deprecated.$s-2;
color: var(--modal-title-foreground-color); color: var(--modal-title-foreground-color);
} }
.reset-btn {
@extend .button-tertiary;
color: var(--button-tertiary-foreground-color-hover);
height: deprecated.$s-28;
border-radius: deprecated.$br-8;
}
.zoom-option { .zoom-option {
@extend .menu-item-base; @extend .menu-item-base;
.shortcuts { .shortcuts {

View File

@ -20,6 +20,9 @@
[app.main.router :as rt] [app.main.router :as rt]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.select :refer [select]] [app.main.ui.components.select :refer [select]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.icons :as deprecated-icon]
[app.util.clipboard :as clipboard] [app.util.clipboard :as clipboard]
[app.util.dom :as dom] [app.util.dom :as dom]
@ -171,10 +174,11 @@
[:div {:class (stl/css :share-link-header)} [:div {:class (stl/css :share-link-header)}
[:h2 {:class (stl/css :share-link-title)} [:h2 {:class (stl/css :share-link-title)}
(tr "common.share-link.title")] (tr "common.share-link.title")]
[:button {:class (stl/css :modal-close-button) [:> icon-button* {:variant "ghost"
:class (stl/css :modal-close-button)
:aria-label (tr "labels.close")
:on-click on-close :on-click on-close
:title (tr "labels.close")} :icon i/close}]]
deprecated-icon/close]]
[:div {:class (stl/css :modal-content)} [:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :share-link-section)} [:div {:class (stl/css :share-link-section)}
(when (and (not confirm?) (some? current-link)) (when (and (not confirm?) (some? current-link))
@ -185,10 +189,10 @@
:placeholder (tr "common.share-link.placeholder") :placeholder (tr "common.share-link.placeholder")
:read-only true}] :read-only true}]
[:button {:class (stl/css :copy-button) [:> icon-button* {:variant "ghost"
:title (tr "viewer.header.share.copy-link") :aria-label (tr "viewer.header.share.copy-link")
:on-click copy-link} :on-click copy-link
deprecated-icon/clipboard]]) :icon i/clipboard}]])
[:div {:class (stl/css :hint-wrapper)} [:div {:class (stl/css :hint-wrapper)}
(when (not ^boolean confirm?) (when (not ^boolean confirm?)
@ -199,28 +203,22 @@
[:div {:class (stl/css :description)} [:div {:class (stl/css :description)}
(tr "common.share-link.confirm-deletion-link-description")] (tr "common.share-link.confirm-deletion-link-description")]
[:div {:class (stl/css :actions)} [:div {:class (stl/css :actions)}
[:input {:type "button" [:> button* {:variant "secondary"
:class (stl/css :button-cancel) :on-click #(reset! confirm* false)}
:on-click #(reset! confirm* false) (tr "labels.cancel")]
:value (tr "labels.cancel")}] [:> button* {:variant "destructive"
[:input {:type "button" :on-click delete-link}
:class (stl/css :button-danger) (tr "common.share-link.destroy-link")]]]
:on-click delete-link
:value (tr "common.share-link.destroy-link")}]]]
(some? current-link) (some? current-link)
[:input [:> button* {:variant "destructive"
{:type "button" :on-click try-delete-link}
:class (stl/css :button-danger) (tr "common.share-link.destroy-link")]
:on-click try-delete-link
:value (tr "common.share-link.destroy-link")}]
:else :else
[:input [:> button* {:variant "primary"
{:type "button" :on-click create-link}
:class (stl/css :button-active) (tr "common.share-link.get-link")])]]
:on-click create-link
:value (tr "common.share-link.get-link")}])]]
(when (not ^boolean confirm?) (when (not ^boolean confirm?)
@ -305,6 +303,7 @@
:options [{:value "team" :label (tr "common.share-link.team-members")} :options [{:value "team" :label (tr "common.share-link.team-members")}
{:value "all" :label (tr "common.share-link.all-users")}] {:value "all" :label (tr "common.share-link.all-users")}]
:on-change on-comment-change}]]] :on-change on-comment-change}]]]
[:div {:class (stl/css :inspect-mode)} [:div {:class (stl/css :inspect-mode)}
[:div {:class (stl/css :subtitle)} [:div {:class (stl/css :subtitle)}
(tr "common.share-link.permissions-can-inspect")] (tr "common.share-link.permissions-can-inspect")]
@ -315,6 +314,3 @@
:options [{:value "team" :label (tr "common.share-link.team-members")} :options [{:value "team" :label (tr "common.share-link.team-members")}
{:value "all" :label (tr "common.share-link.all-users")}] {:value "all" :label (tr "common.share-link.all-users")}]
:on-change on-inspect-change}]]]])])]]])) :on-change on-inspect-change}]]]])])]]]))

View File

@ -30,7 +30,9 @@
} }
.modal-close-button { .modal-close-button {
@extend .modal-close-btn-base; position: absolute;
top: var(--sp-s);
right: var(--sp-s);
} }
.modal-content { .modal-content {
@ -74,18 +76,6 @@
} }
} }
.copy-button {
@extend .button-secondary;
@include deprecated.flexRow;
gap: deprecated.$s-8;
height: deprecated.$s-32;
width: deprecated.$s-28;
svg {
@extend .button-icon;
stroke: var(--icon-foreground-hover);
}
}
.description { .description {
@include deprecated.bodySmallTypography; @include deprecated.bodySmallTypography;
color: var(--modal-text-foreground-color); color: var(--modal-text-foreground-color);
@ -97,18 +87,6 @@
justify-content: flex-end; justify-content: flex-end;
} }
.button-active {
@extend .modal-accept-btn;
}
.button-cancel {
@extend .modal-cancel-btn;
}
.button-danger {
@extend .modal-danger-btn;
}
.permissions-section { .permissions-section {
@include deprecated.flexColumn; @include deprecated.flexColumn;
gap: deprecated.$s-8; gap: deprecated.$s-8;

View File

@ -36,6 +36,7 @@
[app.main.ui.workspace.tokens.import] [app.main.ui.workspace.tokens.import]
[app.main.ui.workspace.tokens.import.modal] [app.main.ui.workspace.tokens.import.modal]
[app.main.ui.workspace.tokens.management.forms.modals] [app.main.ui.workspace.tokens.management.forms.modals]
[app.main.ui.workspace.tokens.remapping-modal]
[app.main.ui.workspace.tokens.settings] [app.main.ui.workspace.tokens.settings]
[app.main.ui.workspace.tokens.themes.create-modal] [app.main.ui.workspace.tokens.themes.create-modal]
[app.main.ui.workspace.viewport :refer [viewport*]] [app.main.ui.workspace.viewport :refer [viewport*]]

View File

@ -25,10 +25,11 @@
[app.main.features :as features] [app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader*]]
[app.main.ui.components.numeric-input :refer [numeric-input*]] [app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.radio-buttons :refer [radio-buttons radio-button]]
[app.main.ui.components.select :refer [select]] [app.main.ui.components.select :refer [select]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
@ -415,24 +416,25 @@
:on-change handle-change-mode}]]) :on-change handle-change-mode}]])
(when (and (= origin :sidebar) show-tokens? token-color) (when (and (= origin :sidebar) show-tokens? token-color)
[:& radio-buttons {:selected color-style [:> radio-buttons* {:selected color-style
:on-change toggle-token-color :on-change toggle-token-color
:name "color-style"} :name "color-style"
[:& radio-button {:icon i/swatches :options [{:id "swap-opt-list"
:value :direct-color :icon i/swatches
:title (tr "labels.color") :label (tr "labels.color")
:id "opt-color"}] :value :direct-color}
[:& radio-button {:icon i/tokens {:id "swap-opt-grid"
:value :token-color :icon i/tokens
:title (tr "workspace.colorpicker.color-tokens") :label (tr "workspace.colorpicker.color-tokens")
:id "opt-token-color"}]])] :value :token-color}]}])]
(when (and (not= selected-mode :image) (when (and (not= selected-mode :image)
(= color-style :direct-color)) (= color-style :direct-color))
[:button {:class (stl/css-case :picker-btn true [:> icon-button* {:variant "ghost"
:selected picking-color?) :aria-label (tr "workspace.colorpicker.get-color")
:on-click handle-click-picker} :aria-pressed picking-color?
deprecated-icon/picker]) :on-click handle-click-picker
:icon i/picker}])
(when (= color-style :token-color) (when (= color-style :token-color)
[:div {:class (stl/css :token-color-title)} [:div {:class (stl/css :token-color-title)}
@ -483,8 +485,7 @@
:aria-label (tr "media.choose-image") :aria-label (tr "media.choose-image")
:on-click on-fill-image-click} :on-click on-fill-image-click}
(tr "media.choose-image") (tr "media.choose-image")
[:& file-uploader [:> file-uploader* {:input-id "fill-image-upload"
{:input-id "fill-image-upload"
:accept "image/jpeg,image/png" :accept "image/jpeg,image/png"
:multi false :multi false
:ref fill-image-ref :ref fill-image-ref

View File

@ -46,52 +46,6 @@
width: px2rem(68); width: px2rem(68);
} }
// TODO: change to DS button component
.picker-btn {
display: flex;
justify-content: center;
align-items: center;
border: none;
background: none;
cursor: pointer;
border-radius: $br-8;
background-color: transparent;
border: $b-1 solid transparent;
height: var(--sp-xl);
width: var(--sp-xl);
border-radius: $br-4;
padding: 0;
margin-top: var(--sp-xs);
svg {
@extend .button-icon;
stroke: var(--button-tertiary-foreground-color-rest);
}
&:hover {
svg {
stroke: var(--button-tertiary-foreground-color-focus);
}
}
&:focus,
&:focus-visible {
outline: none;
svg {
stroke: var(--button-secondary-foreground-color-hover);
}
}
&:active {
outline: none;
border: $b-1 solid transparent;
svg {
stroke: var(--button-tertiary-foreground-color-active);
}
}
&.selected {
svg {
stroke: var(--button-tertiary-foreground-color-active);
}
}
}
.gradient-buttons { .gradient-buttons {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -152,7 +152,6 @@
(when path-set (when path-set
(ptk/data-event :expand-token-sets {:paths path-set})) (ptk/data-event :expand-token-sets {:paths path-set}))
(dwtl/set-selected-token-set-id id) (dwtl/set-selected-token-set-id id)
(dwtl/set-token-type-section-open :color true)
(let [{:keys [modal title]} (get dwta/token-properties :color) (let [{:keys [modal title]} (get dwta/token-properties :color)
window-size (dom/get-window-size) window-size (dom/get-window-size)
left-sidebar (dom/get-element "left-sidebar-aside") left-sidebar (dom/get-element "left-sidebar-aside")

View File

@ -30,6 +30,7 @@
[app.main.ui.components.search-bar :refer [search-bar*]] [app.main.ui.components.search-bar :refer [search-bar*]]
[app.main.ui.components.title-bar :refer [title-bar*]] [app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
@ -44,12 +45,6 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(def ^:private close-icon
(deprecated-icon/icon-xref :close (stl/css :close-icon)))
(def ^:private add-icon
(deprecated-icon/icon-xref :add (stl/css :add-icon)))
(defn- get-library-summary (defn- get-library-summary
"Given a library data return a summary representation of this library" "Given a library data return a summary representation of this library"
[data] [data]
@ -168,12 +163,10 @@
[:div {:class (stl/css :sample-library-item) [:div {:class (stl/css :sample-library-item)
:key (dm/str id)} :key (dm/str id)}
[:div {:class (stl/css :sample-library-item-name)} (:name library)] [:div {:class (stl/css :sample-library-item-name)} (:name library)]
[:input {:class (stl/css-case :sample-library-button true [:> button* {:variant "secondary"
:sample-library-add (nil? importing?) :on-click import-library
:sample-library-adding (some? importing?)) :disabled (some? importing?)}
:type "button" (if (= importing? id) (tr "labels.adding") (tr "labels.add"))]]))
:value (if (= importing? id) (tr "labels.adding") (tr "labels.add"))
:on-click import-library}]]))
(defn- empty-library? (defn- empty-library?
"Check if currentt library summary has elements or not" "Check if currentt library summary has elements or not"
@ -322,14 +315,12 @@
[:> library-description* {:summary summary}]]] [:> library-description* {:summary summary}]]]
(if ^boolean is-shared (if ^boolean is-shared
[:input {:class (stl/css :item-unpublish) [:> button* {:variant "secondary"
:type "button" :on-click unpublish}
:value (tr "common.unpublish") (tr "common.unpublish")]
:on-click unpublish}] [:> button* {:variant "primary"
[:input {:class (stl/css :item-publish) :on-click publish}
:type "button" (tr "common.publish")])]
:value (tr "common.publish")
:on-click publish}])]
(for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries] (for [{:keys [id name data connected-to connected-to-names] :as library} linked-libraries]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)] (let [disabled? (some #(contains? linked-libraries-ids %) connected-to)]
@ -377,12 +368,11 @@
(let [summary (-> (:library-summary library) (let [summary (-> (:library-summary library)
(adapt-backend-summary))] (adapt-backend-summary))]
[:> library-description* {:summary summary}])]] [:> library-description* {:summary summary}])]]
[:> icon-button* {:variant "secondary"
[:button {:class (stl/css :item-button-shared) :aria-label (tr "workspace.libraries.shared-library-btn")
:icon i/add
:data-library-id (dm/str id) :data-library-id (dm/str id)
:title (tr "workspace.libraries.shared-library-btn") :on-click link-library}]])]
:on-click link-library}
add-icon]])]
(when (empty? shared-libraries) (when (empty? shared-libraries)
[:div {:class (stl/css :section-list-empty)} [:div {:class (stl/css :section-list-empty)}
@ -647,11 +637,13 @@
:on-click close-dialog-outside :on-click close-dialog-outside
:data-testid "libraries-modal"} :data-testid "libraries-modal"}
[:div {:class (stl/css :modal-dialog)} [:div {:class (stl/css :modal-dialog)}
[:button {:class (stl/css :close-btn) [:> icon-button* {:variant "ghost"
:on-click close-dialog :class (stl/css :close-btn)
:icon i/close
:aria-label (tr "labels.close") :aria-label (tr "labels.close")
:data-testid "close-libraries"} :data-testid "close-libraries"
close-icon] :on-click close-dialog}]
[:div {:class (stl/css :modal-title)} [:div {:class (stl/css :modal-title)}
(tr "workspace.libraries.libraries")] (tr "workspace.libraries.libraries")]

View File

@ -33,7 +33,7 @@
background-color: var(--modal-background-color); background-color: var(--modal-background-color);
border: $b-2 solid var(--modal-border-color); border: $b-2 solid var(--modal-border-color);
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: 0 auto 1fr;
min-width: $sz-364; min-width: $sz-364;
min-height: $sz-192; min-height: $sz-192;
height: $sz-520; height: $sz-520;
@ -42,21 +42,10 @@
max-width: $sz-712; max-width: $sz-712;
} }
// TODO: Remove this extended creating modal component
.close-btn { .close-btn {
@extend .modal-close-btn-base; position: absolute;
} top: var(--sp-s);
right: var(--sp-s);
.close-icon {
display: flex;
justify-content: center;
align-items: center;
height: $sz-16;
width: $sz-16;
color: transparent;
fill: none;
stroke-width: $b-1;
stroke: var(--icon-foreground);
} }
.modal-title { .modal-title {
@ -120,46 +109,6 @@
height: fit-content; height: fit-content;
} }
.item-publish,
.item-unpublish {
// TODO: remove this extended by using DS button component
@extend .button-primary;
@include t.use-typography("headline-small");
height: $sz-32;
min-width: px2rem(92);
padding: var(--sp-s) var(--sp-xxl);
margin: 0;
border-radius: $br-8;
}
.item-unpublish {
// TODO: remove this extended by using DS button component
@extend .button-secondary;
}
.item-button,
.item-button-shared {
// TODO: remove this extended by using DS button component
@extend .button-secondary;
height: $sz-32;
width: $sz-32;
margin-inline-start: var(--sp-xxs);
padding: var(--sp-s);
}
.detach-icon,
.add-icon {
display: flex;
justify-content: center;
align-items: center;
height: $sz-16;
width: $sz-16;
color: transparent;
fill: none;
stroke-width: $b-1;
stroke: var(--icon-foreground);
}
.section-list-shared { .section-list-shared {
max-height: px2rem(272); max-height: px2rem(272);
} }
@ -170,26 +119,6 @@
color: var(--title-foreground-color); color: var(--title-foreground-color);
} }
.search-icon {
display: flex;
justify-content: center;
align-items: center;
width: px2rem(20);
padding: 0 0 0 var(--sp-s);
svg {
display: flex;
justify-content: center;
align-items: center;
color: transparent;
fill: none;
height: px2rem(12);
width: px2rem(12);
stroke-width: 1.33px;
stroke: var(--icon-foreground);
}
}
// empty state // empty state
.section-list-empty { .section-list-empty {
display: grid; display: grid;
@ -428,24 +357,3 @@
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: px2rem(232); max-width: px2rem(232);
} }
// TODO: Remove this extended using a DS component
.sample-library-add {
@extend .button-secondary;
}
// TODO: Remove this extended using a DS component
.sample-library-adding {
@extend .button-disabled;
}
.sample-library-button {
@include t.use-typography("headline-small");
height: $sz-32;
width: px2rem(80);
margin: 0;
border-radius: $br-8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

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

View File

@ -18,9 +18,10 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.hooks :as h] [app.main.ui.hooks :as h]
[app.main.ui.hooks.resize :as r] [app.main.ui.hooks.resize :as r]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.color-palette :refer [color-palette*]] [app.main.ui.workspace.color-palette :refer [color-palette*]]
[app.main.ui.workspace.color-palette-ctx-menu :refer [color-palette-ctx-menu*]] [app.main.ui.workspace.color-palette-ctx-menu :refer [color-palette-ctx-menu*]]
[app.main.ui.workspace.text-palette :refer [text-palette]] [app.main.ui.workspace.text-palette :refer [text-palette]]
@ -178,27 +179,27 @@
[:ul {:class (dm/str size-classname " " (stl/css-case :palette-btn-list true [:ul {:class (dm/str size-classname " " (stl/css-case :palette-btn-list true
:hidden-bts hide-palettes?))} :hidden-bts hide-palettes?))}
[:li {:class (stl/css :palette-item)} [:li {:class (stl/css :palette-item)}
[:button {:title (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) [:> icon-button* {:variant "ghost"
:aria-pressed (some? color-palette?)
:aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) :aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette))
:class (stl/css-case :palette-btn true :on-click on-select-color-palette
:selected color-palette?) :icon i/drop}]]
:on-click on-select-color-palette}
deprecated-icon/drop-icon]]
[:li {:class (stl/css :palette-item)} [:li {:class (stl/css :palette-item)}
[:button {:title (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) [:> icon-button* {:variant "ghost"
:aria-pressed (some? text-palette?)
:aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) :aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette))
:class (stl/css-case :palette-btn true :on-click on-select-text-palette
:selected text-palette?) :icon i/text-palette}]]]
:on-click on-select-text-palette}
deprecated-icon/text-palette]]]
(if any-palette? (if any-palette?
[:* [:*
[:button {:class (stl/css :palette-actions) [:div {:class (stl/css :menu-btn)}
:on-click #(swap! state* update :show-menu not)} [:> icon-button* {:variant "ghost"
deprecated-icon/menu] :aria-pressed show-menu?
:aria-label (tr "labels.options")
:on-click #(swap! state* update :show-menu not)
:icon i/menu}]]
[:div {:class (stl/css :palette) [:div {:class (stl/css :palette)
:ref container} :ref container}
(when text-palette? (when text-palette?

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