├── .gitignore ├── README.md ├── SketchToolbarIcon.framework ├── Resources │ ├── Assets.car │ └── Info.plist ├── SketchToolbarIcon └── _CodeSignature │ └── CodeResources ├── gh-image.png ├── package-lock.json ├── package.json └── sketch-toolbar-icon.js /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules 3 | .npm 4 | npm-debug.log 5 | 6 | # mac 7 | .DS_Store 8 | 9 | # WebStorm 10 | .idea 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sketch Toolbar Item 2 | An skpm module for adding custom toolbar items in Sketch. 3 | 4 | ![Image](https://github.com/abynim/sketch-toolbar-item/blob/master/gh-image.png?raw=true) 5 | 6 | ## Installation 7 | ```bash 8 | npm install --save sketch-toolbar-item 9 | ``` 10 | 11 | ## Demo 12 | To see the module in action, check out the demo plugin [https://github.com/abynim/sketch-toolbar-item-demo](https://github.com/abynim/sketch-toolbar-item-demo). 13 | 14 | 15 | ## Usage 16 | 17 | ### Import the module 18 | ```js 19 | import SketchToolbar from 'sketch-toolbar-item' 20 | ``` 21 | 22 | ### Define a Startup handler for your plugin in manifest.json 23 | ```json 24 | { 25 | "commands" : [ 26 | { 27 | "script": "./my-command.js", 28 | "handlers": { 29 | "actions": { 30 | "Startup": "registerToolbarActions" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | ``` 37 | 38 | ### Registering toolbar items 39 | All items are registered within the Startup handler 40 | ```js 41 | export function registerToolbarActions(context) { 42 | // register items here... 43 | } 44 | ``` 45 | 46 | #### Register a single toolbar action 47 | Pass in the following arguments 48 | - the context 49 | - the identifier of the command this action will trigger 50 | - and a relative path to an icon image (32x32px) from your plugin's Resources folder. To include a separate image for dark mode, separate the image paths with `|`. 51 | ```js 52 | // SketchToolbar.registerToolbarAction(context, commandIdentifier, iconImagePath) 53 | SketchToolbar.registerToolbarAction(context, 'goodbye', 'goodbye-toolbar-icon.png|goodbye-toolbar-icon-dark.png') 54 | ``` 55 | 56 | #### Register a group of toolbar actions 57 | First, create specifiers for each action, then register them as a group 58 | ```js 59 | // SketchToolbar.specifierForToolbarAction(context, commandIdentifier, iconImagePath) 60 | let item1 = SketchToolbar.specifierForToolbarAction(context, 'namaste', 'namaste-toolbar-icon.png|namaste-toolbar-icon-dark.png') 61 | let item2 = SketchToolbar.specifierForToolbarAction(context, 'hello', 'hello-toolbar-icon.png|hello-toolbar-icon-dark.png') 62 | 63 | // SketchToolbar.registerToolbarGroup(context, groupIdentifier, specifiers) 64 | SketchToolbar.registerToolbarGroup(context, 'salutations', [item1, item2]) 65 | ``` 66 | 67 | #### Register a toolbar item with a dropdown menu 68 | First, create menuItems for each sub-item, then register them as a toolbar menu 69 | ```js 70 | // SketchToolbar.menuItemForToolbarAction(context, commandIdentifier, iconImagePath) 71 | let menuItem1 = SketchToolbar.menuItemForToolbarAction(context, 'hello', 'hello-toolbar-icon.png|hello-toolbar-icon-dark.png') 72 | let menuItem2 = SketchToolbar.menuItemForToolbarAction(context, 'namaste', 'namaste-toolbar-icon.pngnamaste-toolbar-icon-dark.png') 73 | let menuItem3 = SketchToolbar.separatorMenuItem() 74 | let menuItem4 = SketchToolbar.menuItemForToolbarAction(context, 'goodbye', 'goodbye-toolbar-icon.png|goodbye-toolbar-icon-dark.png') 75 | 76 | // SketchToolbar.registerToolbarMenu(context, menuIdentifier, iconImagePath, menuItems) 77 | SketchToolbar.registerToolbarMenu(context, 'greetings', 'Greetings', 'greetings-toolbar-icon.png|greetings-toolbar-icon-dark.png', [menuItem1, menuItem2, menuItem3, menuItem4]) 78 | ``` 79 | 80 | ### Validate toolbar items (optional) 81 | You can validate toolbar items when the selection changes, but please use this sparingly. Doing too much in this method will make Sketch crawl. 82 | 83 | If this method is not defined, your toolbar items will always be enabled (which is acceptable in most situations). 84 | 85 | When defining the command in manifest.json, add an additional handler for `ValidateToolbarItem`. 86 | 87 | ```json 88 | { 89 | "commands" : [ 90 | { 91 | "script": "./my-command.js", 92 | "handlers": { 93 | "run": "sayHello", 94 | "ValidateToolbarItem": "validateToolbarItem" 95 | }, 96 | "name": "Hello", 97 | "identifier": "hello" 98 | } 99 | ] 100 | } 101 | ``` 102 | 103 | In the handler, access the toolbar item via the context, and set its enabled property. 104 | ```js 105 | export function validateToolbarItem(context) { 106 | 107 | let toolbarItem = context.toolbarItem 108 | 109 | // As an example: enable the toolbar item if selection is not empty 110 | toolbarItem.enabled = !sketch.getSelectedDocument().selectedLayers.isEmpty 111 | 112 | } 113 | ``` 114 | 115 | ### Note: 116 | Please set a unique identifier for your plugin in your manifest. It's required to identify your plugin and avoid conflicts with other plugins. If using `skpm` this is already handled for you. 117 | 118 | --- 119 | 120 | Do create an issue here if you find any weirdness. 121 | 122 | Follow along on [Twitter](https://twitter.com/abynim) for more experiments and content related to Sketch plugins. I spend most of my time building a little plugin called [Sketch Runner](https://sketchrunner.com), do check it out. 123 | -------------------------------------------------------------------------------- /SketchToolbarIcon.framework/Resources/Assets.car: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abynim/sketch-toolbar-item/511e386aaf819d72d7d85d1d4eecdb828f78f06e/SketchToolbarIcon.framework/Resources/Assets.car -------------------------------------------------------------------------------- /SketchToolbarIcon.framework/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 19H2 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | SketchToolbarIcon 11 | CFBundleIdentifier 12 | com.abynim.SketchToolbarIcon 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | SketchToolbarIcon 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 119 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 12A7300 31 | DTPlatformName 32 | macosx 33 | DTPlatformVersion 34 | 10.15.6 35 | DTSDKBuild 36 | 19G68 37 | DTSDKName 38 | macosx10.15 39 | DTXcode 40 | 1201 41 | DTXcodeBuild 42 | 12A7300 43 | LSMinimumSystemVersion 44 | 10.14 45 | NSHumanReadableCopyright 46 | Copyright © 2019 Aby Nimbalkar. All rights reserved. 47 | 48 | 49 | -------------------------------------------------------------------------------- /SketchToolbarIcon.framework/SketchToolbarIcon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abynim/sketch-toolbar-item/511e386aaf819d72d7d85d1d4eecdb828f78f06e/SketchToolbarIcon.framework/SketchToolbarIcon -------------------------------------------------------------------------------- /SketchToolbarIcon.framework/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | Resources/Assets.car 8 | 9 | stFi/ljN8Ipl9NwM6WQHnli9Ir4= 10 | 11 | Resources/Info.plist 12 | 13 | Z7U3vd+DcEwsgBWIu0Rijjj1nQo= 14 | 15 | 16 | files2 17 | 18 | Resources/Assets.car 19 | 20 | hash2 21 | 22 | Uldi7Lns+DkHx50WHk8ghcQQFlRjvecvPuRf7QJgxAM= 23 | 24 | 25 | Resources/Info.plist 26 | 27 | hash2 28 | 29 | 2n0Ty5vOsLxwFq0DPYCEABEuoHmOFOetn1JN/wRNAqM= 30 | 31 | 32 | 33 | rules 34 | 35 | ^Resources/ 36 | 37 | ^Resources/.*\.lproj/ 38 | 39 | optional 40 | 41 | weight 42 | 1000 43 | 44 | ^Resources/.*\.lproj/locversion.plist$ 45 | 46 | omit 47 | 48 | weight 49 | 1100 50 | 51 | ^Resources/Base\.lproj/ 52 | 53 | weight 54 | 1010 55 | 56 | ^version.plist$ 57 | 58 | 59 | rules2 60 | 61 | .*\.dSYM($|/) 62 | 63 | weight 64 | 11 65 | 66 | ^(.*/)?\.DS_Store$ 67 | 68 | omit 69 | 70 | weight 71 | 2000 72 | 73 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ 74 | 75 | nested 76 | 77 | weight 78 | 10 79 | 80 | ^.* 81 | 82 | ^Info\.plist$ 83 | 84 | omit 85 | 86 | weight 87 | 20 88 | 89 | ^PkgInfo$ 90 | 91 | omit 92 | 93 | weight 94 | 20 95 | 96 | ^Resources/ 97 | 98 | weight 99 | 20 100 | 101 | ^Resources/.*\.lproj/ 102 | 103 | optional 104 | 105 | weight 106 | 1000 107 | 108 | ^Resources/.*\.lproj/locversion.plist$ 109 | 110 | omit 111 | 112 | weight 113 | 1100 114 | 115 | ^Resources/Base\.lproj/ 116 | 117 | weight 118 | 1010 119 | 120 | ^[^/]+$ 121 | 122 | nested 123 | 124 | weight 125 | 10 126 | 127 | ^embedded\.provisionprofile$ 128 | 129 | weight 130 | 20 131 | 132 | ^version\.plist$ 133 | 134 | weight 135 | 20 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /gh-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abynim/sketch-toolbar-item/511e386aaf819d72d7d85d1d4eecdb828f78f06e/gh-image.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sketch-toolbar-item", 3 | "version": "0.1.4", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@skpm/nib-loader": { 8 | "version": "0.1.2", 9 | "resolved": "https://registry.npmjs.org/@skpm/nib-loader/-/nib-loader-0.1.2.tgz", 10 | "integrity": "sha512-qWj31AmzEO9+BlozMaZo7HOK6v2VAkOmj9wSMaPwM54tmHDKKE5p1F+MbcAsibsfbbTfodK5PKkpimf1GyjHiA==", 11 | "dev": true, 12 | "requires": { 13 | "cocoascript-class": "^0.1.2", 14 | "loader-utils": "^1.2.3", 15 | "schema-utils": "^0.4.5" 16 | } 17 | }, 18 | "@skpm/xcodeproj-loader": { 19 | "version": "0.1.6", 20 | "resolved": "https://registry.npmjs.org/@skpm/xcodeproj-loader/-/xcodeproj-loader-0.1.6.tgz", 21 | "integrity": "sha512-EuGG1cm1gjMLRLImgeJ40KyWJ1P69Qz9oNp+fKn1GaHWz3vWV3568PidtzMx3H3UM3i5bXUroMmIfj7DQaI/Dg==", 22 | "dev": true, 23 | "requires": { 24 | "@skpm/nib-loader": "^0.1.2", 25 | "loader-utils": "^1.2.3", 26 | "schema-utils": "^0.4.5" 27 | } 28 | }, 29 | "ajv": { 30 | "version": "6.10.1", 31 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", 32 | "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", 33 | "dev": true, 34 | "requires": { 35 | "fast-deep-equal": "^2.0.1", 36 | "fast-json-stable-stringify": "^2.0.0", 37 | "json-schema-traverse": "^0.4.1", 38 | "uri-js": "^4.2.2" 39 | } 40 | }, 41 | "ajv-keywords": { 42 | "version": "3.4.1", 43 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", 44 | "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", 45 | "dev": true 46 | }, 47 | "big.js": { 48 | "version": "5.2.2", 49 | "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", 50 | "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", 51 | "dev": true 52 | }, 53 | "cocoascript-class": { 54 | "version": "0.1.2", 55 | "resolved": "https://registry.npmjs.org/cocoascript-class/-/cocoascript-class-0.1.2.tgz", 56 | "integrity": "sha1-2rJfIDiZRtmYbBgSuIrDeD7sQtM=", 57 | "dev": true 58 | }, 59 | "emojis-list": { 60 | "version": "2.1.0", 61 | "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", 62 | "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", 63 | "dev": true 64 | }, 65 | "fast-deep-equal": { 66 | "version": "2.0.1", 67 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 68 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 69 | "dev": true 70 | }, 71 | "fast-json-stable-stringify": { 72 | "version": "2.0.0", 73 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 74 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 75 | "dev": true 76 | }, 77 | "json-schema-traverse": { 78 | "version": "0.4.1", 79 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 80 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 81 | "dev": true 82 | }, 83 | "json5": { 84 | "version": "1.0.1", 85 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", 86 | "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", 87 | "dev": true, 88 | "requires": { 89 | "minimist": "^1.2.0" 90 | } 91 | }, 92 | "loader-utils": { 93 | "version": "1.2.3", 94 | "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", 95 | "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", 96 | "dev": true, 97 | "requires": { 98 | "big.js": "^5.2.2", 99 | "emojis-list": "^2.0.0", 100 | "json5": "^1.0.1" 101 | } 102 | }, 103 | "minimist": { 104 | "version": "1.2.5", 105 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 106 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 107 | "dev": true 108 | }, 109 | "punycode": { 110 | "version": "2.1.1", 111 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 112 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 113 | "dev": true 114 | }, 115 | "schema-utils": { 116 | "version": "0.4.7", 117 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", 118 | "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", 119 | "dev": true, 120 | "requires": { 121 | "ajv": "^6.1.0", 122 | "ajv-keywords": "^3.1.0" 123 | } 124 | }, 125 | "uri-js": { 126 | "version": "4.2.2", 127 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 128 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 129 | "dev": true, 130 | "requires": { 131 | "punycode": "^2.1.0" 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sketch-toolbar-item", 3 | "version": "0.1.5", 4 | "description": "A module for adding toolbar items in Sketch", 5 | "main": "sketch-toolbar-icon.js", 6 | "scripts": { 7 | "test": "echo \"No test specified\"" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/abynim/sketch-toolbar-item.git" 12 | }, 13 | "author": "Aby Nimbalkar", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/abynim/sketch-toolbar-item/issues" 17 | }, 18 | "homepage": "https://github.com/abynim/sketch-toolbar-item#readme", 19 | "devDependencies": { 20 | "@skpm/xcodeproj-loader": "^0.1.6" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sketch-toolbar-icon.js: -------------------------------------------------------------------------------- 1 | const SketchToolbarIconClass = require('@skpm/xcodeproj-loader?raw=true&publicPath=_webpack_resources&outputPath=../Resources/_webpack_resources!./SketchToolbarIcon.framework/SketchToolbarIcon').getClass('SketchToolbarIcon'); 2 | 3 | module.exports = function() { 4 | 5 | let o = {}; 6 | 7 | /** 8 | * @param {any} context - The current context 9 | * @param {string} commandID - The identifier of the command this item will trigger 10 | * @param {string} iconImagePath - A relative path to a 32x32px png image. To include a separate image path for dark mode use | to separate their path names 11 | */ 12 | o.registerToolbarAction = function(context, commandID, iconImagePath) { 13 | SketchToolbarIconClass.registerToolbarAction_commandID_iconImagePath(context, commandID, iconImagePath); 14 | } 15 | 16 | /** 17 | * @param {any} context - The current context 18 | * @param {string} commandID - The identifier of the command this item will trigger 19 | * @param {string} iconImagePath - A relative path to a 32x32px png image. To include a separate image path for dark mode use | to separate their path names 20 | * @returns {any} A toolbar item specifier which can be used to register a toolbar item group 21 | */ 22 | o.specifierForToolbarAction = function(context, commandID, iconImagePath) { 23 | return SketchToolbarIconClass.specifierForToolbarAction_commandID_iconImagePath(context, commandID, iconImagePath); 24 | } 25 | 26 | /** 27 | * @param {any} context - The current context 28 | * @param {string} identifier - A unique identifier for the group item 29 | * @param {Array} specifiers - An array of specifiers created using `specifierForToolbarAction` 30 | */ 31 | o.registerToolbarGroup = function (context, identifier, specifiers) { 32 | SketchToolbarIconClass.registerToolbarGroup_identifier_specifiers(context, identifier, specifiers); 33 | } 34 | 35 | /** 36 | * @param {any} context - The current context 37 | * @param {string} commandID - A unique identifier for the group item 38 | * @param {string} iconImagePath - A relative path to a 32x32px png image. To include a separate image path for dark mode use | to separate their path names 39 | * @returns {any} A menu item specifier to be used when registering a toolbar item with a dropdown menu 40 | */ 41 | o.menuItemForToolbarAction = function(context, commandID, iconImagePath) { 42 | return SketchToolbarIconClass.menuItemForToolbarAction_commandID_iconImagePath(context, commandID, iconImagePath); 43 | } 44 | 45 | /** 46 | * @returns {any} A separator menu item specifier to be used when registering a toolbar item with a dropdown menu 47 | */ 48 | o.separatorMenuItem = function () { 49 | return SketchToolbarIconClass.separatorMenuItem(); 50 | } 51 | 52 | /** 53 | * @param {any} context - The current context 54 | * @param {string} identifier - A unique identifier for the toolbar item 55 | * @param {string} title - The text to be displayed below in the toolbar item 56 | * @param {string} iconImagePath - A relative path to a 32x32px png image. To include a separate image path for dark mode use | to separate their path names 57 | * @param {Array} menuItems - An array of menu item specifiers created using `menuItemForToolbarAction` or `separatorMenuItem` 58 | */ 59 | o.registerToolbarMenu = function (context, identifier, title, iconImagePath, menuItems) { 60 | SketchToolbarIconClass.registerToolbarMenu_identifier_title_iconImagePath_menuItems(context, identifier, title, iconImagePath, menuItems); 61 | } 62 | 63 | return o; 64 | 65 | }(); --------------------------------------------------------------------------------