├── entry.js ├── register.js ├── preset.js ├── register-options.js ├── .gitignore ├── src ├── preset.ts ├── download-file.ts ├── register.tsx ├── entry.ts └── components │ └── sketch-download.tsx ├── .babelrc ├── typings └── global.d.ts ├── tsconfig.json ├── LICENSE ├── .github └── workflows │ └── push.yml ├── README.md ├── package.json └── CHANGELOG.md /entry.js: -------------------------------------------------------------------------------- 1 | require('./dist/entry'); 2 | -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | require('./dist/register').default(); 2 | -------------------------------------------------------------------------------- /preset.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/preset'); 2 | -------------------------------------------------------------------------------- /register-options.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/register').default; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage 3 | .vscode 4 | dist/ 5 | yarn-error.log 6 | .env -------------------------------------------------------------------------------- /src/preset.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | addons: ['storybook-addon-sketch/register'], 3 | entries: ['storybook-addon-sketch/entry'] 4 | } 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react", 5 | "@babel/preset-typescript" 6 | ], 7 | "plugins": ["@babel/plugin-proposal-class-properties"], 8 | "sourceMaps": true 9 | } 10 | -------------------------------------------------------------------------------- /src/download-file.ts: -------------------------------------------------------------------------------- 1 | import { saveAs } from 'file-saver'; 2 | 3 | const downloadFile = (file: string, json: object) => { 4 | const blob = new Blob([JSON.stringify(json)], { 5 | type: 'text/plain;charset=utf-8' 6 | }); 7 | 8 | saveAs(blob, file); 9 | }; 10 | 11 | export default downloadFile; 12 | -------------------------------------------------------------------------------- /typings/global.d.ts: -------------------------------------------------------------------------------- 1 | interface PageSymbol {} 2 | 3 | interface Page { 4 | layers: PageSymbol[]; 5 | } 6 | 7 | interface Window { 8 | saveCurrent: (title: string) => void; 9 | 10 | page2layers: { 11 | getPage: (options: { 12 | width: number; 13 | height: number; 14 | title: string; 15 | }) => Page; 16 | getSymbol: (options: { 17 | fixPseudo: boolean; 18 | removePreviewMargin: boolean; 19 | }) => PageSymbol; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/register.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import addons from '@storybook/addons'; 3 | import SketchPlugin, { Options } from './components/sketch-download'; 4 | 5 | export default (options: Options) => 6 | addons.register('sketch', api => { 7 | addons.add('sketch/panel', { 8 | type: 'tool', 9 | title: 'Sketch', 10 | match: ({ viewMode }) => viewMode === 'story', 11 | render: () => 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "baseUrl": ".", 5 | "jsx": "preserve", 6 | "lib": ["dom", "esnext"], 7 | "outDir": "dist", 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "declaration": true, 13 | "noUnusedLocals": true, 14 | "preserveConstEnums": true, 15 | "removeComments": false, 16 | "skipLibCheck": true, 17 | "sourceMap": true, 18 | "strict": true, 19 | "target": "esnext", 20 | "typeRoots": ["./node_modules/@types", "./typings", "typings/markdown.d.ts"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/entry.ts: -------------------------------------------------------------------------------- 1 | import downloadFile from './download-file'; 2 | 3 | window.saveCurrent = (title: string) => { 4 | const page2layers = document.createElement('script'); 5 | page2layers.src = 6 | 'https://unpkg.com/story2sketch@1.5.0/lib/browser/page2layers.bundle.js'; 7 | page2layers.type = 'text/javascript'; 8 | page2layers.onload = () => { 9 | const page = window.page2layers.getPage({ 10 | title, 11 | width: 1920, 12 | height: 5000 13 | }); 14 | 15 | page.layers = [ 16 | window.page2layers.getSymbol({ 17 | fixPseudo: true, 18 | removePreviewMargin: true 19 | }) 20 | ]; 21 | 22 | downloadFile(`${title}.asketch.json`, page); 23 | }; 24 | 25 | document.head.appendChild(page2layers); 26 | }; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Andrew Lisowski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | default: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Skip CI 11 | uses: veggiemonk/skip-commit@master 12 | env: 13 | COMMIT_FILTER: skip ci 14 | - name: Prepare repository 15 | env: 16 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 17 | run: | 18 | git checkout "${GITHUB_REF:11}" -- 19 | git remote rm origin 20 | git remote add origin "https://$GH_TOKEN@github.com/intuit/storybook-addon-sketch.git" 21 | git fetch origin 22 | git branch --set-upstream-to origin/master 23 | - name: Use Node.js 10.x 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: 10.x 27 | - name: Build 28 | run: | 29 | git remote -v 30 | yarn install --frozen-lockfile 31 | yarn build 32 | - name: Release 33 | env: 34 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | run: | 37 | yarn release 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # storybook-addon-sketch 2 | 3 | > Download sketch files straight from storybook. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add -D storybook-addon-sketch 9 | # or 10 | npm i --save storybook-addon-sketch 11 | ``` 12 | 13 | ## Configuration 14 | 15 | There are two ways you can install this addon. 16 | 17 | ### Preset 18 | 19 | Add the following to your `presets.js`. 20 | 21 | ```js 22 | module.exports = ['storybook-addon-sketch/preset']; 23 | ``` 24 | 25 | ### Manually 26 | 27 | Add the following to your `addons.js`. 28 | 29 | ```js 30 | import 'storybook-addon-sketch/register'; 31 | ``` 32 | 33 | Add the following to your storybook `webpack.config.js`. 34 | 35 | ```js 36 | module.exports = ({ config }) => { 37 | config.entry.push(require.resolve('storybook-addon-sketch/entry')); 38 | }; 39 | ``` 40 | 41 | And you are all done! 42 | 43 | ## Options 44 | 45 | To configure any options for `storybook-addon-sketch` change your `addons.js` to the following 46 | 47 | ```js 48 | require('storybook-addon-sketch/register-options')(options); 49 | ``` 50 | 51 | ### Stories for Kind 52 | 53 | If you want to let your storybook users also download all the default renderings for a story kind (ex: Input). 54 | 55 | ```js 56 | require('storybook-addon-sketch/register-options')({ kind: true }); 57 | ``` 58 | 59 | You will also need to have ran [story2sketch](https://github.com/chrisvxd/story2sketch) with the option `outputBy` set to `kind` and the `output` set to `out/sketch`. This will output all the sketch files to a specific folder that users can download from. 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook-addon-sketch", 3 | "version": "0.2.1", 4 | "description": "Get the sketch files for your stories in Storybook", 5 | "main": "dist/index.js", 6 | "source": "src/index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/intuit/storybook-addon-sketch" 10 | }, 11 | "files": [ 12 | "dist", 13 | "entry.js", 14 | "preset.js", 15 | "register.js", 16 | "register-options.js" 17 | ], 18 | "browserslist": "> 0.25%, not dead", 19 | "author": { 20 | "name": "Andrew Lisowski", 21 | "email": "lisowski54@gmail.com" 22 | }, 23 | "publishConfig": { 24 | "registry": "https://registry.npmjs.org" 25 | }, 26 | "scripts": { 27 | "build": "npm run build:types && npm run build:js", 28 | "build:watch": "npm run build:js -- --watch --verbose", 29 | "build:js": "babel src --out-dir dist --extensions \".ts,.tsx\"", 30 | "type-check": "tsc --noEmit", 31 | "build:types": "tsc --emitDeclarationOnly", 32 | "release": "auto shipit" 33 | }, 34 | "devDependencies": { 35 | "@babel/cli": "^7.4.3", 36 | "@babel/core": "^7.4.3", 37 | "@babel/plugin-proposal-class-properties": "^7.4.0", 38 | "@babel/preset-env": "^7.4.3", 39 | "@babel/preset-react": "^7.0.0", 40 | "@babel/preset-typescript": "^7.3.3", 41 | "@storybook/addons": "^5.2.0", 42 | "@storybook/components": "^5.2.0", 43 | "@types/file-saver": "^2.0.0", 44 | "@types/node": "^12.7.5", 45 | "@types/react": "^16.8.14", 46 | "auto": "^7.6.0", 47 | "husky": "^2.0.0", 48 | "jest": "^24.7.1", 49 | "lint-staged": "^8.1.5", 50 | "prettier": "^1.17.0", 51 | "react": "^16.8.6", 52 | "react-dom": "^16.8.6", 53 | "typescript": "^3.4.5" 54 | }, 55 | "peerDependencies": { 56 | "@storybook/addons": ">= 5.x", 57 | "@storybook/components": ">= 5.x", 58 | "react": "*" 59 | }, 60 | "dependencies": { 61 | "@types/url-join": "^4.0.0", 62 | "file-saver": "^2.0.1", 63 | "url-join": "^4.0.0" 64 | }, 65 | "prettier": { 66 | "singleQuote": true 67 | }, 68 | "husky": { 69 | "hooks": { 70 | "pre-commit": "lint-staged" 71 | } 72 | }, 73 | "lint-staged": { 74 | "*.{js,json,css,md}": [ 75 | "prettier --write", 76 | "git add" 77 | ] 78 | }, 79 | "jest": { 80 | "collectCoverage": true, 81 | "coverageDirectory": "coverage", 82 | "coverageReporters": [ 83 | "json", 84 | "lcov", 85 | "text", 86 | "html" 87 | ] 88 | }, 89 | "license": "MIT", 90 | "auto": { 91 | "plugins": [ 92 | "npm", 93 | "released" 94 | ], 95 | "labels": { 96 | "dependency-update": { 97 | "name": "dependency-update", 98 | "title": "Dependency Updates" 99 | } 100 | }, 101 | "skipReleaseLabels": [ 102 | "dependency-update" 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.2.1 (Fri Jan 15 2021) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - Fix typo in description [#13](https://github.com/intuit/storybook-addon-sketch/pull/13) ([@kaelig](https://github.com/kaelig)) 6 | 7 | #### Authors: 1 8 | 9 | - Kaelig Deloumeau-Prigent ([@kaelig](https://github.com/kaelig)) 10 | 11 | --- 12 | 13 | # v0.2.0 (Mon Jun 08 2020) 14 | 15 | #### 🚀 Enhancement 16 | 17 | - simplify preset [#8](https://github.com/intuit/storybook-addon-sketch/pull/8) ([@hipstersmoothie](https://github.com/hipstersmoothie)) 18 | 19 | #### Authors: 1 20 | 21 | - Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie)) 22 | 23 | --- 24 | 25 | # v0.1.1 (Mon Jun 08 2020) 26 | 27 | #### 🐛 Bug Fix 28 | 29 | - Remove story2sketch dependency [#3](https://github.com/intuit/storybook-addon-sketch/pull/3) ([@mcrosby114](https://github.com/mcrosby114)) 30 | 31 | #### ⚠️ Pushed to master 32 | 33 | - Update package.json ([@hipstersmoothie](https://github.com/hipstersmoothie)) 34 | 35 | #### Authors: 2 36 | 37 | - Matthew Crosby ([@mcrosby114](https://github.com/mcrosby114)) 38 | - Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie)) 39 | 40 | --- 41 | 42 | # v0.1.0 (Mon Sep 23 2019) 43 | 44 | #### 🚀 Enhancement 45 | 46 | - add preset [#1](https://github.com/intuit/storybook-addon-sketch/pull/1) ([@hipstersmoothie](https://github.com/hipstersmoothie)) 47 | 48 | #### ⚠️ Pushed to master 49 | 50 | - Revert "delete workflows" ([@hipstersmoothie](https://github.com/hipstersmoothie)) 51 | - delete workflows ([@hipstersmoothie](https://github.com/hipstersmoothie)) 52 | - update url ([@hipstersmoothie](https://github.com/hipstersmoothie)) 53 | - :pray: ([@hipstersmoothie](https://github.com/hipstersmoothie)) 54 | - de-intuit it ([@hipstersmoothie](https://github.com/hipstersmoothie)) 55 | 56 | #### Authors: 1 57 | 58 | - Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie)) 59 | 60 | --- 61 | 62 | # v0.0.2 (Wed Sep 18 2019) 63 | 64 | #### 🐛 Bug Fix 65 | 66 | - make storybook related deps peers [#2](https://github.intuit.com/design-systems/storybook-addon-sketch/pull/2) ([@alisowski](https://github.intuit.com/alisowski)) 67 | 68 | #### Authors: 1 69 | 70 | - Andrew Lisowski ([@alisowski](https://github.intuit.com/alisowski)) 71 | 72 | --- 73 | 74 | # v0.0.1 (Wed Apr 24 2019) 75 | 76 | #### ⚠️ Pushed to master 77 | 78 | - scope package ([@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com)) 79 | - update config to point to internal github ([@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com)) 80 | - update author ([@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com)) 81 | - add repo ([@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com)) 82 | - add ci ([@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com)) 83 | - add docs and add `kind` option ([@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com)) 84 | 85 | #### Authors: 1 86 | 87 | - [@lisowski54@gmail.com](https://github.intuit.com/lisowski54@gmail.com) -------------------------------------------------------------------------------- /src/components/sketch-download.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import join from 'url-join'; 3 | 4 | import { 5 | Icons, 6 | IconButton, 7 | WithTooltip, 8 | TooltipLinkList 9 | } from '@storybook/components'; 10 | import downloadFile from '../download-file'; 11 | 12 | const downloadKind = (api: any) => () => { 13 | const withoutIndex = window.location.pathname 14 | .split('/') 15 | .slice(0, -1) 16 | .join('/'); 17 | const baseUrl = join(window.location.origin, withoutIndex); 18 | const kind = api 19 | .getCurrentStoryData() 20 | .kind.replace(/ /g, '_') 21 | .replace(/\//g, '+'); 22 | const file = kind + '.asketch.json'; 23 | const url = join(baseUrl, 'sketch', file); 24 | 25 | fetch(url) 26 | .then(data => data.json()) 27 | .then(json => downloadFile(file, json)); 28 | }; 29 | 30 | const downloadCurrent = (api: any) => () => { 31 | const iframe = document.querySelector('iframe'); 32 | 33 | if (iframe && iframe.contentWindow) { 34 | iframe.contentWindow.saveCurrent(api.getCurrentStoryData().id); 35 | } 36 | }; 37 | 38 | const createBackgroundSelectorItem = (id: string, click: () => void) => ({ 39 | id: id, 40 | title: id, 41 | onClick: click, 42 | value: id 43 | }); 44 | 45 | export interface Options { 46 | kind: boolean; 47 | } 48 | 49 | interface SketchPluginProps { 50 | api: any; 51 | options: Options; 52 | } 53 | 54 | export default class SketchPlugin extends React.Component { 55 | state = { 56 | selected: null, 57 | expanded: false 58 | }; 59 | 60 | static defaultProps = { 61 | options: {} 62 | }; 63 | 64 | change = (options: { selected: string }) => 65 | this.setState({ selected: options.selected, expanded: false }); 66 | 67 | onVisibilityChange = (expanded: boolean) => { 68 | if (this.state.expanded !== expanded) { 69 | this.setState({ expanded }); 70 | } 71 | }; 72 | 73 | render() { 74 | const Button: React.FC> = props => ( 75 | 76 | 77 | 78 | ); 79 | 80 | if (!this.props.options.kind) { 81 | return