0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 05:33:02 -05:00

New UI for the plugin (#90)

* add figma-create-plugin ui

* first attempt

* more changes

* update packages

* fix stuff

* implement reload action

* simplify code

* create wrapper

* fix logo

* adjust sizes

* add changelog

* update design again

* temporary fix

---------

Co-authored-by: Alex Sánchez <sion333@gmail.com>
This commit is contained in:
Jordi Sala Morales 2024-05-09 12:56:45 +02:00 committed by GitHub
parent a18f98151a
commit 3ee244db92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 757 additions and 316 deletions

View file

@ -0,0 +1,5 @@
---
"penpot-exporter": minor
---
Rethink the UI to be more usable

518
package-lock.json generated
View file

@ -9,8 +9,9 @@
"version": "0.2.2", "version": "0.2.2",
"license": "MPL2.0", "license": "MPL2.0",
"dependencies": { "dependencies": {
"react": "^18.3", "@create-figma-plugin/ui": "^3.2",
"react-dom": "^18.3", "classnames": "^2.5",
"preact": "^10.21",
"react-hook-form": "^7.51", "react-hook-form": "^7.51",
"romans": "^2.0", "romans": "^2.0",
"slugify": "^1.6", "slugify": "^1.6",
@ -21,9 +22,8 @@
"@changesets/cli": "^2.27", "@changesets/cli": "^2.27",
"@figma/eslint-plugin-figma-plugins": "^0.15", "@figma/eslint-plugin-figma-plugins": "^0.15",
"@figma/plugin-typings": "^1.92", "@figma/plugin-typings": "^1.92",
"@preact/preset-vite": "^2.8",
"@trivago/prettier-plugin-sort-imports": "^4.3", "@trivago/prettier-plugin-sort-imports": "^4.3",
"@types/react": "^18.3",
"@types/react-dom": "^18.3",
"@types/svg-path-parser": "^1.1", "@types/svg-path-parser": "^1.1",
"@typescript-eslint/eslint-plugin": "^7.8", "@typescript-eslint/eslint-plugin": "^7.8",
"@typescript-eslint/parser": "^7.8", "@typescript-eslint/parser": "^7.8",
@ -191,6 +191,32 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
"integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/types": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.1",
"@babel/helper-validator-identifier": "^7.24.5",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.23.6", "version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
@ -338,6 +364,15 @@
"@babel/core": "^7.0.0" "@babel/core": "^7.0.0"
} }
}, },
"node_modules/@babel/helper-plugin-utils": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
"integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-simple-access": { "node_modules/@babel/helper-simple-access": {
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
@ -400,9 +435,9 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20", "version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -508,6 +543,69 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
"integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz",
"integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-jsx": "^7.23.3",
"@babel/types": "^7.23.4"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-development": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz",
"integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==",
"dev": true,
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx/node_modules/@babel/types": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.1",
"@babel/helper-validator-identifier": "^7.24.5",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.24.4", "version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
@ -1003,6 +1101,33 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/@create-figma-plugin/ui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@create-figma-plugin/ui/-/ui-3.2.0.tgz",
"integrity": "sha512-E0is67aaoVECDgSMAlkp584wI24yglENH6M4OIJwDMbHBXidIbtAk9RIbUDr0gC+2NWNRLpIyFQH6LnZcqO+0A==",
"dependencies": {
"@create-figma-plugin/utilities": "^3.2.0"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"preact": ">=10"
}
},
"node_modules/@create-figma-plugin/utilities": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@create-figma-plugin/utilities/-/utilities-3.2.0.tgz",
"integrity": "sha512-+5JddDqI6XjVJs0EqgLd+6B9ukjhHFEBesqPI/e0okqS+Ay7Gt3xsD0vMrZjVYfJcmRAprgJhdGBGJZjOZiQIA==",
"dependencies": {
"hex-rgb": "^5.0.0",
"natural-compare-lite": "1.4.0",
"rgb-hex": "^4.1.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@csstools/css-parser-algorithms": { "node_modules/@csstools/css-parser-algorithms": {
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz",
@ -1493,6 +1618,120 @@
"url": "https://opencollective.com/unts" "url": "https://opencollective.com/unts"
} }
}, },
"node_modules/@preact/preset-vite": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.8.2.tgz",
"integrity": "sha512-m3tl+M8IO8jgiHnk+7LSTFl8axdPXloewi7iGVLdmCwf34XOzEUur0bZVewW4DUbUipFjTS2CXu27+5f/oexBA==",
"dev": true,
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.22.15",
"@babel/plugin-transform-react-jsx-development": "^7.22.5",
"@prefresh/vite": "^2.4.1",
"@rollup/pluginutils": "^4.1.1",
"babel-plugin-transform-hook-names": "^1.0.2",
"debug": "^4.3.4",
"kolorist": "^1.8.0",
"magic-string": "0.30.5",
"node-html-parser": "^6.1.10",
"resolve": "^1.22.8",
"source-map": "^0.7.4",
"stack-trace": "^1.0.0-pre2"
},
"peerDependencies": {
"@babel/core": "7.x",
"vite": "2.x || 3.x || 4.x || 5.x"
}
},
"node_modules/@preact/preset-vite/node_modules/@rollup/pluginutils": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
"dev": true,
"dependencies": {
"estree-walker": "^2.0.1",
"picomatch": "^2.2.2"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/@preact/preset-vite/node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/@preact/preset-vite/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@prefresh/babel-plugin": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.1.tgz",
"integrity": "sha512-uG3jGEAysxWoyG3XkYfjYHgaySFrSsaEb4GagLzYaxlydbuREtaX+FTxuIidp241RaLl85XoHg9Ej6E4+V1pcg==",
"dev": true
},
"node_modules/@prefresh/core": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.2.tgz",
"integrity": "sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA==",
"dev": true,
"peerDependencies": {
"preact": "^10.0.0"
}
},
"node_modules/@prefresh/utils": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.0.tgz",
"integrity": "sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==",
"dev": true
},
"node_modules/@prefresh/vite": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.5.tgz",
"integrity": "sha512-iForDVJ2M8gQYnm5pHumvTEJjGGc7YNYC0GVKnHFL+GvFfKHfH9Rpq67nUAzNbjuLEpqEOUuQVQajMazWu2ZNQ==",
"dev": true,
"dependencies": {
"@babel/core": "^7.22.1",
"@prefresh/babel-plugin": "0.5.1",
"@prefresh/core": "^1.5.1",
"@prefresh/utils": "^1.2.0",
"@rollup/pluginutils": "^4.2.1"
},
"peerDependencies": {
"preact": "^10.4.0",
"vite": ">=2.0.0"
}
},
"node_modules/@prefresh/vite/node_modules/@rollup/pluginutils": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
"integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
"dev": true,
"dependencies": {
"estree-walker": "^2.0.1",
"picomatch": "^2.2.2"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/@rollup/pluginutils": { "node_modules/@rollup/pluginutils": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
@ -1937,31 +2176,6 @@
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
"dev": true "dev": true
}, },
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"dev": true
},
"node_modules/@types/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz",
"integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.5.8", "version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@ -2692,6 +2906,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/babel-plugin-transform-hook-names": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz",
"integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==",
"dev": true,
"peerDependencies": {
"@babel/core": "^7.12.10"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -2710,6 +2933,12 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -2893,6 +3122,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@ -3110,6 +3344,22 @@
"node": ">=12 || >=16" "node": ">=12 || >=16"
} }
}, },
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-tree": { "node_modules/css-tree": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@ -3123,6 +3373,18 @@
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
} }
}, },
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"dev": true,
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/cssesc": { "node_modules/cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -3135,12 +3397,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/csv": { "node_modules/csv": {
"version": "5.5.3", "version": "5.5.3",
"resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz", "resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz",
@ -3383,6 +3639,61 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dot-case": { "node_modules/dot-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
@ -4615,6 +4926,26 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true,
"bin": {
"he": "bin/he"
}
},
"node_modules/hex-rgb": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-5.0.0.tgz",
"integrity": "sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/hosted-git-info": { "node_modules/hosted-git-info": {
"version": "2.8.9", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@ -5164,7 +5495,8 @@
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
@ -5283,6 +5615,12 @@
"integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==", "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==",
"dev": true "dev": true
}, },
"node_modules/kolorist": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"dev": true
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -5382,6 +5720,7 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": { "dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0" "js-tokens": "^3.0.0 || ^4.0.0"
}, },
@ -5410,6 +5749,18 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/map-obj": { "node_modules/map-obj": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
@ -5549,6 +5900,11 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"node_modules/natural-compare-lite": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g=="
},
"node_modules/no-case": { "node_modules/no-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@ -5579,6 +5935,16 @@
} }
} }
}, },
"node_modules/node-html-parser": {
"version": "6.1.13",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
"dev": true,
"dependencies": {
"css-select": "^5.1.0",
"he": "1.2.0"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.14", "version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@ -5632,6 +5998,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -6095,6 +6473,15 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true "dev": true
}, },
"node_modules/preact": {
"version": "10.21.0",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.21.0.tgz",
"integrity": "sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/preferred-pm": { "node_modules/preferred-pm": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.1.3.tgz", "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.1.3.tgz",
@ -6202,26 +6589,13 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "18.3.1", "name": "@preact/compat",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "version": "17.1.2",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "resolved": "https://registry.npmjs.org/@preact/compat/-/compat-17.1.2.tgz",
"dependencies": { "integrity": "sha512-7pOZN9lMDDRQ+6aWvjwTp483KR8/zOpfS83wmOo3zfuLKdngS8/5RLbsFWzFZMGdYlotAhX980hJ75bjOHTwWg==",
"loose-envify": "^1.1.0" "peer": true,
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": { "peerDependencies": {
"react": "^18.3.1" "preact": "*"
} }
}, },
"node_modules/react-hook-form": { "node_modules/react-hook-form": {
@ -6502,6 +6876,17 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/rgb-hex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/rgb-hex/-/rgb-hex-4.1.0.tgz",
"integrity": "sha512-UZLM57BW09Yi9J1R3OP8B1yCbbDK3NT8BDtihGZkGkGEs2b6EaV85rsfJ6yK4F+8UbxFFmfA+9xHT5ZWhN1gDQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -6629,14 +7014,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true "dev": true
}, },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.0", "version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@ -7145,6 +7522,15 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true "dev": true
}, },
"node_modules/stack-trace": {
"version": "1.0.0-pre2",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz",
"integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==",
"dev": true,
"engines": {
"node": ">=16"
}
},
"node_modules/stream-transform": { "node_modules/stream-transform": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz",

View file

@ -23,8 +23,9 @@
"author": "Kaleidos", "author": "Kaleidos",
"license": "MPL2.0", "license": "MPL2.0",
"dependencies": { "dependencies": {
"react": "^18.3", "@create-figma-plugin/ui": "^3.2",
"react-dom": "^18.3", "classnames": "^2.5",
"preact": "^10.21",
"react-hook-form": "^7.51", "react-hook-form": "^7.51",
"romans": "^2.0", "romans": "^2.0",
"slugify": "^1.6", "slugify": "^1.6",
@ -35,9 +36,8 @@
"@changesets/cli": "^2.27", "@changesets/cli": "^2.27",
"@figma/eslint-plugin-figma-plugins": "^0.15", "@figma/eslint-plugin-figma-plugins": "^0.15",
"@figma/plugin-typings": "^1.92", "@figma/plugin-typings": "^1.92",
"@preact/preset-vite": "^2.8",
"@trivago/prettier-plugin-sort-imports": "^4.3", "@trivago/prettier-plugin-sort-imports": "^4.3",
"@types/react": "^18.3",
"@types/react-dom": "^18.3",
"@types/svg-path-parser": "^1.1", "@types/svg-path-parser": "^1.1",
"@typescript-eslint/eslint-plugin": "^7.8", "@typescript-eslint/eslint-plugin": "^7.8",
"@typescript-eslint/parser": "^7.8", "@typescript-eslint/parser": "^7.8",

View file

@ -1,9 +1,9 @@
import { transformDocumentNode } from '@plugin/transformers';
import { findAllTextNodes } from './findAllTextnodes'; import { findAllTextNodes } from './findAllTextnodes';
import { setCustomFontId } from './translators/text/font/custom'; import { handleExportMessage } from './handleExportMessage';
import { BASE_WIDTH, LOADING_HEIGHT } from './pluginSizes';
import { registerChange } from './registerChange';
figma.showUI(__html__, { themeColors: true, height: 300, width: 400 }); figma.showUI(__html__, { themeColors: true, width: BASE_WIDTH, height: LOADING_HEIGHT });
figma.ui.onmessage = message => { figma.ui.onmessage = message => {
if (message.type === 'ready') { if (message.type === 'ready') {
@ -17,17 +17,12 @@ figma.ui.onmessage = message => {
if (message.type === 'cancel') { if (message.type === 'cancel') {
figma.closePlugin(); figma.closePlugin();
} }
if (message.type === 'reload') {
findAllTextNodes();
}
}; };
const handleExportMessage = async (missingFontIds: Record<string, string>) => { figma.on('currentpagechange', () => {
await figma.loadAllPagesAsync(); figma.currentPage.once('nodechange', registerChange);
});
Object.entries(missingFontIds).forEach(([fontFamily, fontId]) => {
setCustomFontId(fontFamily, fontId);
});
figma.ui.postMessage({
type: 'PENPOT_DOCUMENT',
data: await transformDocumentNode(figma.root)
});
};

View file

@ -1,3 +1,10 @@
import {
BASE_WIDTH,
MISSING_FONTS_TEXT_HEIGHT,
MISSING_SINGLE_FONT_HEIGHT,
NORMAL_HEIGHT
} from './pluginSizes';
import { registerChange } from './registerChange';
import { isGoogleFont } from './translators/text/font/gfonts'; import { isGoogleFont } from './translators/text/font/gfonts';
import { isLocalFont } from './translators/text/font/local'; import { isLocalFont } from './translators/text/font/local';
@ -27,14 +34,10 @@ export const findAllTextNodes = async () => {
data: Array.from(fonts) data: Array.from(fonts)
}); });
const maxHeight = 300; const newHeight =
NORMAL_HEIGHT +
(fonts.size > 0 ? MISSING_FONTS_TEXT_HEIGHT + fonts.size * MISSING_SINGLE_FONT_HEIGHT : 0);
if (fonts.size === 0) return; figma.ui.resize(BASE_WIDTH, newHeight);
figma.currentPage.once('nodechange', registerChange);
if (fonts.size * 40 > maxHeight) {
figma.ui.resize(400, 300 + maxHeight);
return;
}
figma.ui.resize(400, 300 + fonts.size * 40);
}; };

View file

@ -0,0 +1,15 @@
import { transformDocumentNode } from '@plugin/transformers';
import { setCustomFontId } from '@plugin/translators/text/font/custom';
export const handleExportMessage = async (missingFontIds: Record<string, string>) => {
await figma.loadAllPagesAsync();
Object.entries(missingFontIds).forEach(([fontFamily, fontId]) => {
setCustomFontId(fontFamily, fontId);
});
figma.ui.postMessage({
type: 'PENPOT_DOCUMENT',
data: await transformDocumentNode(figma.root)
});
};

View file

@ -0,0 +1,8 @@
export const LOADING_HEIGHT = 130;
export const NORMAL_HEIGHT = LOADING_HEIGHT + 5;
export const BASE_WIDTH = 290;
export const MISSING_FONTS_TEXT_HEIGHT = 195;
export const MISSING_SINGLE_FONT_HEIGHT = 65;
export const NEEDS_RELOAD_TEXT_HEIGHT = 90;

View file

@ -0,0 +1,6 @@
import { BASE_WIDTH, NEEDS_RELOAD_TEXT_HEIGHT, NORMAL_HEIGHT } from './pluginSizes';
export const registerChange = () => {
figma.ui.postMessage({ type: 'CHANGES_DETECTED' });
figma.ui.resize(BASE_WIDTH, NORMAL_HEIGHT + NEEDS_RELOAD_TEXT_HEIGHT);
};

View file

@ -1,12 +1,21 @@
import Logo from '@ui/assets/logo.svg?react'; import Penpot from '@ui/assets/penpot.svg?react';
import { PenpotExporter } from '@ui/components/PenpotExporter'; import { PenpotExporter } from '@ui/components/PenpotExporter';
import { Stack } from '@ui/components/Stack';
import { Wrapper } from './components/Wrapper/Wrapper';
export const App = () => ( export const App = () => (
<main> <Wrapper>
<header> <Stack space="medium">
<Logo /> <Penpot
<h2>Penpot Exporter</h2> style={{
</header> alignSelf: 'center',
<PenpotExporter /> height: 'auto',
</main> width: '8.125rem',
fill: 'var(--figma-color-icon)'
}}
/>
<PenpotExporter />
</Stack>
</Wrapper>
); );

View file

@ -1 +0,0 @@
<svg height="500" viewBox="0 0 500 500.00001" width="500" xmlns="http://www.w3.org/2000/svg"><path d="m159.4607 552.36219-52.57675 74.05348v41.86098l-45.753774 21.76184-.412151-.19478v17.20283 255.89707l178.379885 84.27239 10.90209 5.1462 10.89926-5.1462 178.38271-84.27239v-273.0999l-.33593.15808-45.76789-21.76749v-41.81863l-1.60059-2.25268-50.97899-71.8008-52.57958 74.05348v.0734l-38.25894-53.88377-37.96254 53.4688-1.35782-1.91111zm9.3015 43.01555 20.33627 28.64128h-59.27553l20.09914-28.30535zm181.13787 0 20.33626 28.64128h-59.27553l20.09632-28.30535zm-90.83852 20.24593 20.33626 28.63846h-59.2727l20.09631-28.30535zm-134.85903 22.82891h28.11339v94.66356l-28.11339-13.2818zm42.54695 0h27.97224l-.003 114.69495-27.97224-13.21405zm138.58809 0h28.11622l-.003 101.38492-28.11057 13.27898-.003-114.6639zm42.54695 0h27.97224v81.3507l-27.97224 13.21406zm-133.38265 20.24311h28.11339v117.07749l-28.11339-13.2818zm42.54695 0h27.97225l-.003 104.02152-27.97224 13.21688.003-117.2384zm136.12651 31.11133 24.75131 10.12014-24.75131 11.6925zm-286.29137.0367v21.80982l-24.748483-11.6925zm-24.367392 34.4113 156.581352 73.96879v224.87888l-156.581352-73.96877zm334.964042 0v224.8789l-156.58134 73.96877v-224.87888z" transform="translate(0 -552.3622)"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

1
ui-src/assets/penpot.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,9 +0,0 @@
type LoaderProps = {
loading: boolean;
};
export const Loader = ({ loading }: LoaderProps) => {
if (!loading) return;
return <section>Checking for missing fonts...</section>;
};

View file

@ -1,34 +1,61 @@
import { useFormContext } from 'react-hook-form'; import { Banner, IconInfo32, Link, Textbox } from '@create-figma-plugin/ui';
import { Controller, useFormContext } from 'react-hook-form';
import { Stack } from './Stack';
type MissingFontsSectionProps = { type MissingFontsSectionProps = {
fonts?: string[]; fonts?: string[];
}; };
export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => { export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => {
const { register } = useFormContext(); if (!fonts || !fonts.length) return null;
if (fonts === undefined || !fonts.length) return;
return ( return (
<section className="missing-fonts-section"> <Stack space="small">
<div className="missing-fonts-header"> <Stack space="xsmall">
{fonts.length} missing font{fonts.length > 1 ? 's' : ''}:{' '} <Banner icon={<IconInfo32 />}>
</div> {fonts.length} custom font{fonts.length > 1 ? 's' : ''} detected
<small className="font-install-message"> </Banner>
Ensure fonts are installed in Penpot before exporting. <span>To export your file with custom fonts, please follow these steps:</span>
</small> <Stack as="ol" space="xsmall" style={{ paddingLeft: '1rem' }}>
<div className="missing-fonts-list"> <li>
{fonts.map(font => ( Upload your local fonts in Penpot.
<div key={font} className="font-input-row"> <br />
<span className="font-name">{font}</span> <Link href="https://www.google.com" target="_blank" rel="noreferrer">
<input Learn how to do it.
className="font-id-input" </Link>
placeholder="Enter Penpot font id" </li>
{...register(font)} <li>Copy and paste the font IDs from Penpot below.</li>
/> </Stack>
</div> </Stack>
))} {fonts.map(font => (
</div> <Stack space="2xsmall" key={font}>
</section> <ControlledTextbox name={font} placeholder="Paste font ID from Penpot" />
<span>{font}</span>
</Stack>
))}
</Stack>
);
};
type ControlledTextboxProps = { name: string; placeholder: string };
const ControlledTextbox = ({ name, placeholder }: ControlledTextboxProps) => {
const { control } = useFormContext();
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value } }) => (
<Textbox
onChange={onChange}
onBlur={onBlur}
value={value}
placeholder={placeholder}
variant="border"
/>
)}
/>
); );
}; };

View file

@ -1,16 +1,19 @@
import { Banner, Button, IconInfo32, LoadingIndicator } from '@create-figma-plugin/ui';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { Stack } from '@ui/components/Stack';
import { createPenpotFile } from '@ui/converters'; import { createPenpotFile } from '@ui/converters';
import { PenpotDocument } from '@ui/lib/types/penpotDocument'; import { PenpotDocument } from '@ui/lib/types/penpotDocument';
import { Loader } from './Loader';
import { MissingFontsSection } from './MissingFontsSection'; import { MissingFontsSection } from './MissingFontsSection';
type FormValues = Record<string, string>; type FormValues = Record<string, string>;
export const PenpotExporter = () => { export const PenpotExporter = () => {
const [missingFonts, setMissingFonts] = useState<string[]>(); const [missingFonts, setMissingFonts] = useState<string[]>();
const [needsReload, setNeedsReload] = useState(false);
const [loading, setLoading] = useState(true);
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const methods = useForm<FormValues>(); const methods = useForm<FormValues>();
@ -26,6 +29,10 @@ export const PenpotExporter = () => {
setExporting(false); setExporting(false);
} else if (event.data.pluginMessage?.type == 'CUSTOM_FONTS') { } else if (event.data.pluginMessage?.type == 'CUSTOM_FONTS') {
setMissingFonts(event.data.pluginMessage.data as string[]); setMissingFonts(event.data.pluginMessage.data as string[]);
setLoading(false);
setNeedsReload(false);
} else if (event.data.pluginMessage?.type == 'CHANGES_DETECTED') {
setNeedsReload(true);
} }
}; };
@ -47,6 +54,11 @@ export const PenpotExporter = () => {
parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*'); parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
}; };
const reload = () => {
setLoading(true);
parent.postMessage({ pluginMessage: { type: 'reload' } }, '*');
};
useEffect(() => { useEffect(() => {
window.addEventListener('message', onMessage); window.addEventListener('message', onMessage);
@ -57,21 +69,43 @@ export const PenpotExporter = () => {
}; };
}, []); }, []);
const pluginReady = missingFonts !== undefined; if (loading) {
return <LoadingIndicator />;
}
if (needsReload) {
return (
<Stack space="small">
<Banner icon={<IconInfo32 />}>
Changes detected. Please reload the plug-in to ensure all modifications are included in
the exported file.
</Banner>
<Stack space="xsmall" direction="row">
<Button onClick={reload} fullWidth>
Reload
</Button>
<Button secondary onClick={cancel} fullWidth>
Cancel
</Button>
</Stack>
</Stack>
);
}
return ( return (
<FormProvider {...methods}> <FormProvider {...methods}>
<form className="centered-form" onSubmit={methods.handleSubmit(exportPenpot)}> <form onSubmit={methods.handleSubmit(exportPenpot)}>
<Loader loading={!pluginReady} /> <Stack space="medium">
<div className="missing-fonts-form-container">
<MissingFontsSection fonts={missingFonts} /> <MissingFontsSection fonts={missingFonts} />
</div> <Stack space="xsmall" direction="row">
<footer> <Button type="submit" loading={exporting} fullWidth>
<button type="submit" className="brand" disabled={exporting || !pluginReady}> Export to Penpot
{exporting ? 'Exporting...' : 'Export to Penpot'} </Button>
</button> <Button secondary onClick={cancel} fullWidth>
<button onClick={cancel}>Cancel</button> Cancel
</footer> </Button>
</Stack>
</Stack>
</form> </form>
</FormProvider> </FormProvider>
); );

View file

@ -0,0 +1,32 @@
.stack {
--direction: column;
--spacing: 1.5rem;
display: flex;
flex-direction: var(--direction);
gap: var(--spacing);
& > * {
flex: 1;
}
.stack-row {
--direction: row;
}
.stack-small {
--spacing: 1rem;
}
.stack-xsmall {
--spacing: 0.5rem;
}
.stack-2xsmall {
--spacing: 0.25rem;
}
.stack-center {
align-items: center;
}
}

View file

@ -0,0 +1,37 @@
import classNames from 'classnames';
import { CSSProperties, PropsWithChildren } from 'react';
import styles from './Stack.module.css';
type StackProps = PropsWithChildren & {
space?: 'medium' | 'small' | 'xsmall' | '2xsmall';
direction?: 'column' | 'row';
horizontalAlign?: 'start' | 'center';
style?: CSSProperties;
as?: 'div' | 'ol';
};
export const Stack = ({
space = 'medium',
direction = 'column',
horizontalAlign = 'start',
style,
as = 'div',
children
}: StackProps) => {
const Tag = as;
return (
<Tag
className={classNames({
[styles.stack]: true,
[styles[`stack-${space}`]]: space !== 'medium',
[styles[`stack-${direction}`]]: direction !== 'column',
[styles[`stack-${horizontalAlign}`]]: horizontalAlign !== 'start'
})}
style={style}
>
{children}
</Tag>
);
};

View file

@ -0,0 +1 @@
export * from './Stack';

View file

@ -0,0 +1,3 @@
.wrapper {
margin: 1.5rem 1rem 0;
}

View file

@ -0,0 +1,15 @@
import { CSSProperties, PropsWithChildren } from 'react';
import styles from './Wrapper.module.css';
type WrapperProps = PropsWithChildren & {
style?: CSSProperties;
};
export const Wrapper = ({ style, children }: WrapperProps) => {
return (
<div className={styles.wrapper} style={style}>
{children}
</div>
);
};

View file

@ -1,176 +1,40 @@
:root { *,
--color-bg: var(--figma-color-bg); *::before,
--color-bg-hover: var(--figma-color-bg-hover); *::after {
--color-bg-active: var(--figma-color-bg-pressed); box-sizing: border-box;
--color-border: var(--figma-color-border);
--color-border-focus: var(--figma-color-border-selected);
--color-icon: var(--figma-color-icon);
--color-text: var(--figma-color-text);
--color-bg-brand: var(--figma-color-bg-brand);
--color-bg-brand-hover: var(--figma-color-bg-brand-hover);
--color-bg-brand-active: var(--figma-color-bg-brand-pressed);
--color-border-brand: var(--figma-color-border-brand);
--color-border-brand-focus: var(--figma-color-border-selected-strong);
--color-text-brand: var(--figma-color-text-onbrand);
} }
html, * {
body,
main {
height: 100%;
}
body,
input,
button {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 1rem;
text-align: center;
}
body {
background: var(--color-bg);
color: var(--color-text);
margin: 0; margin: 0;
} }
button { body {
border-radius: 0.25rem; line-height: 1.5;
background: var(--color-bg); -webkit-font-smoothing: antialiased;
color: var(--color-text);
cursor: pointer;
border: 1px solid var(--color-border);
padding: 0.5rem 1rem;
}
button:hover {
background-color: var(--color-bg-hover);
}
button:active {
background-color: var(--color-bg-active);
}
button:focus-visible {
border: none;
outline-color: var(--color-border-focus);
}
button:disabled {
background-color: black;
color: var(--color-text);
cursor: not-allowed;
}
button.brand {
--color-bg: var(--color-bg-brand);
--color-text: var(--color-text-brand);
--color-bg-hover: var(--color-bg-brand-hover);
--color-bg-active: var(--color-bg-brand-active);
--color-border: transparent;
--color-border-focus: var(--color-border-brand-focus);
}
input {
background: 1px solid var(--color-bg);
border: 1px solid var(--color-border);
color: 1px solid var(--color-text);
padding: 0.5rem;
}
input:focus-visible {
border-color: var(--color-border-focus);
outline-color: var(--color-border-focus);
} }
img,
picture,
video,
canvas,
svg { svg {
stroke: var(--color-icon, rgb(0 0 0 / 90%)); display: block;
height: auto; max-width: 100%;
width: 2rem;
} }
main { input,
align-items: center; button,
display: flex; textarea,
flex-direction: column; select {
justify-content: center; font: inherit;
} }
section { p,
align-items: center; h1,
display: flex; h2,
flex-direction: column; h3,
justify-content: center; h4,
margin-bottom: 1rem; h5,
} h6 {
overflow-wrap: break-word;
section > * + * {
margin-top: 0.5rem;
}
footer > * + * {
margin-left: 0.5rem;
}
.missing-fonts small {
font-size: 0.5rem;
padding-top: 5px;
padding-bottom: 5px;
}
ul {
padding: 0;
list-style-type: none;
}
.centered-form {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.missing-fonts-form-container {
width: 100%;
margin: 0 auto 10px;
max-height: 300px;
overflow: auto;
}
.missing-fonts-section {
width: 100%;
max-width: 500px;
margin: 0 auto;
}
.missing-fonts-list {
display: flex;
flex-direction: column;
align-items: stretch;
width: 90%;
}
.font-input-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
}
.font-name {
flex: 0 0 auto;
width: 100%;
max-width: 30%;
text-align: center;
white-space: normal;
}
.font-id-input {
flex: 1;
width: 70%;
max-width: 70%;
margin-left: 10px;
padding: 5px;
box-sizing: border-box;
} }

View file

@ -1,3 +1,4 @@
import 'node_modules/@create-figma-plugin/ui/lib/css/base.css';
import { StrictMode } from 'react'; import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';

View file

@ -4,8 +4,15 @@
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"paths": {
"@plugin/*": ["./plugin-src/*"],
"@ui/*": ["./ui-src/*"],
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"],
"react-dom/client": ["./node_modules/preact/compat/client"]
},
"allowJs": false, "allowJs": false,
"skipLibCheck": false, "skipLibCheck": true,
"esModuleInterop": false, "esModuleInterop": false,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
@ -14,6 +21,7 @@
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
} }
} }

View file

@ -1,4 +1,4 @@
import react from '@vitejs/plugin-react-swc'; import preact from '@preact/preset-vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import { viteSingleFile } from 'vite-plugin-singlefile'; import { viteSingleFile } from 'vite-plugin-singlefile';
import svgr from 'vite-plugin-svgr'; import svgr from 'vite-plugin-svgr';
@ -6,7 +6,7 @@ import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({ export default defineConfig({
root: './ui-src', root: './ui-src',
plugins: [svgr(), react(), viteSingleFile(), tsconfigPaths()], plugins: [svgr(), preact(), viteSingleFile(), tsconfigPaths()],
build: { build: {
target: 'esnext', target: 'esnext',
assetsInlineLimit: 100000000, assetsInlineLimit: 100000000,
@ -16,7 +16,8 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
output: { output: {
inlineDynamicImports: true inlineDynamicImports: true
} },
external: ['!../css/base.css']
} }
} }
}); });