Repository: airbnb/react-sketchapp Branch: master Commit: b238e69c6f1e Files: 320 Total size: 477.5 KB Directory structure: gitextract_dx66tsc9/ ├── .bookignore ├── .editorconfig ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__/ │ ├── jest/ │ │ ├── components/ │ │ │ ├── Artboard.tsx │ │ │ ├── Document.tsx │ │ │ ├── Image.tsx │ │ │ ├── Page.tsx │ │ │ ├── RedBox.tsx │ │ │ ├── Svg.tsx │ │ │ ├── Text.tsx │ │ │ ├── View.tsx │ │ │ ├── __snapshots__/ │ │ │ │ ├── Artboard.tsx.snap │ │ │ │ ├── Document.tsx.snap │ │ │ │ ├── Image.tsx.snap │ │ │ │ ├── Page.tsx.snap │ │ │ │ ├── RedBox.tsx.snap │ │ │ │ ├── Svg.tsx.snap │ │ │ │ ├── Text.tsx.snap │ │ │ │ └── View.tsx.snap │ │ │ └── nodeImpl/ │ │ │ ├── Svg.tsx │ │ │ └── __snapshots__/ │ │ │ └── Svg.tsx.snap │ │ ├── index.ts │ │ ├── jsonUtils/ │ │ │ ├── computeTextTree.ts │ │ │ ├── computeYogaNode.ts │ │ │ ├── computeYogaTree.ts │ │ │ ├── layerGroup.ts │ │ │ ├── models.ts │ │ │ ├── shapeLayers.ts │ │ │ └── style.ts │ │ ├── reactTreeToFlexTree.ts │ │ ├── sharedStyles/ │ │ │ └── TextStyles.ts │ │ └── utils/ │ │ ├── isDefined.ts │ │ ├── sortObjectKeys.ts │ │ └── zIndex.ts │ └── skpm/ │ ├── basic.test.js │ ├── render-context.test.js │ └── render-in-wrapped-object.test.js ├── book.json ├── docs/ │ ├── API.md │ ├── FAQ.md │ ├── README.md │ ├── examples.md │ └── guides/ │ ├── README.md │ ├── data-fetching.md │ ├── getting-started.md │ ├── rendering.md │ ├── styling.md │ ├── universal-rendering.md │ └── using-skpm.md ├── examples/ │ ├── .eslintrc │ ├── .gitignore │ ├── basic-setup/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── manifest.json │ │ │ └── my-command.js │ │ └── webpack.skpm.config.js │ ├── basic-setup-typescript/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── manifest.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── my-command.tsx │ │ │ └── types/ │ │ │ └── sketch.d.ts │ │ ├── tsconfig.json │ │ └── webpack.skpm.config.js │ ├── basic-svg/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── manifest.json │ │ │ └── my-command.js │ │ └── webpack.skpm.config.js │ ├── colors/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── main.js │ │ │ └── manifest.json │ │ └── webpack.skpm.config.js │ ├── emotion/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── manifest.json │ │ │ └── my-command.js │ │ └── webpack.skpm.config.js │ ├── form-validation/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── Button.js │ │ │ │ ├── Register.js │ │ │ │ ├── Space.js │ │ │ │ ├── StrengthMeter.js │ │ │ │ └── TextBox/ │ │ │ │ ├── index.js │ │ │ │ ├── index.sketch.js │ │ │ │ └── style.js │ │ │ ├── data.js │ │ │ ├── designSystem.js │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── web.js │ │ └── webpack.skpm.config.js │ ├── foursquare-maps/ │ │ ├── .eslintrc │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.js │ │ │ ├── getVenues.js │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── web.js │ │ └── webpack.skpm.config.js │ ├── glamorous/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── manifest.json │ │ │ └── my-command.js │ │ └── webpack.skpm.config.js │ ├── profile-cards/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── Profile.js │ │ │ │ └── Space.js │ │ │ ├── designSystem.js │ │ │ ├── main.js │ │ │ └── manifest.json │ │ └── webpack.skpm.config.js │ ├── profile-cards-graphql/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── Profile.js │ │ │ │ └── Space.js │ │ │ ├── designSystem.js │ │ │ ├── main.js │ │ │ └── manifest.json │ │ └── webpack.skpm.config.js │ ├── profile-cards-primitives/ │ │ ├── README.md │ │ ├── nwb.config.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── Profile.js │ │ │ │ └── Space.js │ │ │ ├── data.js │ │ │ ├── designSystem.js │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── web.js │ │ └── webpack.skpm.config.js │ ├── profile-cards-react-with-styles/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ └── Profile.js │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ ├── theme.js │ │ │ ├── types.js │ │ │ └── withStyles.js │ │ └── webpack.skpm.config.js │ ├── react-router-prototyping/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── App.js │ │ │ ├── components/ │ │ │ │ ├── AppBar.js │ │ │ │ └── NavBar.js │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── routes/ │ │ │ ├── about.js │ │ │ ├── home.js │ │ │ ├── post.js │ │ │ └── profile.js │ │ └── webpack.skpm.config.js │ ├── styled-components/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── manifest.json │ │ │ └── my-command.js │ │ └── webpack.skpm.config.js │ ├── styleguide/ │ │ ├── .flowconfig │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── AccessibilityBadge.js │ │ │ │ ├── Badge.js │ │ │ │ ├── Label.js │ │ │ │ ├── Palette.js │ │ │ │ ├── Section.js │ │ │ │ ├── Swatch.js │ │ │ │ └── TypeSpecimen.js │ │ │ ├── designSystem.js │ │ │ ├── main.js │ │ │ ├── manifest.json │ │ │ └── processColor.js │ │ └── webpack.skpm.config.js │ ├── symbols/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── manifest.json │ │ │ └── my-command.js │ │ └── webpack.skpm.config.js │ └── timeline-airtable/ │ ├── .eslintrc │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── main.js │ │ └── manifest.json │ └── webpack.skpm.config.js ├── jest.config.js ├── package.json ├── prettier.config.js ├── src/ │ ├── Platform.ts │ ├── buildTree.ts │ ├── components/ │ │ ├── Artboard.tsx │ │ ├── Document.tsx │ │ ├── Image.tsx │ │ ├── ImageStylePropTypes.ts │ │ ├── Page.tsx │ │ ├── PageStylePropTypes.ts │ │ ├── RedBox.tsx │ │ ├── ResizeModePropTypes.ts │ │ ├── ResizingConstraintPropTypes.ts │ │ ├── ShadowsPropTypes.ts │ │ ├── Svg/ │ │ │ ├── Circle.tsx │ │ │ ├── ClipPath.tsx │ │ │ ├── Defs.tsx │ │ │ ├── Ellipse.tsx │ │ │ ├── G.tsx │ │ │ ├── Image.tsx │ │ │ ├── Line.tsx │ │ │ ├── LinearGradient.tsx │ │ │ ├── Path.tsx │ │ │ ├── Pattern.tsx │ │ │ ├── Polygon.tsx │ │ │ ├── Polyline.tsx │ │ │ ├── RadialGradient.tsx │ │ │ ├── Rect.tsx │ │ │ ├── Stop.tsx │ │ │ ├── Svg.tsx │ │ │ ├── Symbol.tsx │ │ │ ├── TSpan.tsx │ │ │ ├── Text.tsx │ │ │ ├── TextPath.tsx │ │ │ ├── Use.tsx │ │ │ ├── index.tsx │ │ │ └── props.ts │ │ ├── Text.tsx │ │ ├── TextStylePropTypes.ts │ │ ├── View.tsx │ │ ├── ViewStylePropTypes.ts │ │ └── index.ts │ ├── context.tsx │ ├── entrypoint.sketch.ts │ ├── entrypoint.ts │ ├── flexToSketchJSON.ts │ ├── index.ts │ ├── jsonUtils/ │ │ ├── borders.ts │ │ ├── computeTextTree.ts │ │ ├── computeYogaNode.ts │ │ ├── computeYogaTree.ts │ │ ├── hotspotLayer.ts │ │ ├── layerGroup.ts │ │ ├── makeSvgLayer/ │ │ │ ├── graphics/ │ │ │ │ ├── curvePoint.ts │ │ │ │ ├── path.ts │ │ │ │ ├── point.ts │ │ │ │ ├── rect.ts │ │ │ │ └── types.ts │ │ │ ├── index.sketch.ts │ │ │ └── index.ts │ │ ├── models.ts │ │ ├── resizeConstraint.ts │ │ ├── shapeLayers.ts │ │ ├── sketchJson/ │ │ │ ├── fromSJSON.ts │ │ │ └── toSJSON.ts │ │ ├── style.ts │ │ └── textLayers.ts │ ├── platformBridges/ │ │ ├── macos.ts │ │ └── sketch/ │ │ ├── createStringMeasurer.ts │ │ ├── findFontName.ts │ │ ├── index.ts │ │ └── makeImageDataFromUrl.ts │ ├── render.tsx │ ├── renderToJSON.ts │ ├── renderers/ │ │ ├── ArtboardRenderer.ts │ │ ├── ImageRenderer.ts │ │ ├── SketchRenderer.ts │ │ ├── SvgRenderer.ts │ │ ├── SymbolInstanceRenderer.ts │ │ ├── SymbolMasterRenderer.ts │ │ ├── TextRenderer.ts │ │ ├── ViewRenderer.ts │ │ └── index.ts │ ├── resets.ts │ ├── sharedStyles/ │ │ └── TextStyles.ts │ ├── stylesheet/ │ │ ├── expandStyle.ts │ │ ├── index.ts │ │ └── types.ts │ ├── symbol.tsx │ ├── types/ │ │ ├── globals.d.ts │ │ ├── index.ts │ │ ├── intrinsic.d.ts │ │ ├── js-sha1.d.ts │ │ ├── murmur2js.d.ts │ │ ├── node-sketch-bridge.d.ts │ │ └── normalize-css-color.d.ts │ └── utils/ │ ├── Context.ts │ ├── constants.ts │ ├── createStringMeasurer.ts │ ├── getDocument.ts │ ├── getImageDataFromURL.ts │ ├── getSketchVersion.ts │ ├── hasAnyDefined.ts │ ├── hashStyle.ts │ ├── isDefined.ts │ ├── isNativeDocument.ts │ ├── isNativePage.ts │ ├── isNativeSymbolsPage.ts │ ├── pick.ts │ ├── processTransform/ │ │ ├── index.ts │ │ ├── matrix2D.ts │ │ ├── parseTransformOriginProp.ts │ │ └── parseTransformProp.ts │ ├── same.ts │ ├── sharedTextStyles/ │ │ ├── index.sketch.ts │ │ └── index.ts │ ├── sortObjectKeys.ts │ └── zIndex.ts ├── template/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ └── src/ │ ├── manifest.json │ └── my-command.js ├── tsconfig.json └── tsconfig.module.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bookignore ================================================ .github/ __tests__/ examples/ lib/ scratch/ src/ ================================================ FILE: .editorconfig ================================================ # http://editorconfig.org root = true [*] indent_style = space indent_size = 2 charset = utf-8 trim_trailing_whitespace = true end_of_line = lf insert_final_newline = true ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jon.gold@airbnb.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing Contributions are welcome and are greatly appreciated! Every little bit helps, and credit will always be given. By contributing, you agree to abide by the [code of conduct](https://github.com/airbnb/react-sketchapp/blob/master/.github/CODE_OF_CONDUCT.md). ## Reporting Issues and Asking Questions **For support or usage questions like “how do I do X with react-sketchapp” and “my code doesn't work”, please search and ask on [StackOverflow with a react-sketchapp tag](http://stackoverflow.com/questions/tagged/react-sketchapp?sort=votes&pageSize=50) first.** We ask you to do this because StackOverflow does a much better job at keeping popular questions visible. Unfortunately good answers get lost and outdated on GitHub. Some questions take a long time to get an answer. **If your question gets closed or you don't get a reply on StackOverflow for longer than a few days,** we encourage you to post an issue linking to your question. We will close your issue but this will give people watching the repo an opportunity to see your question and reply to it on StackOverflow if they know the answer. Please be considerate when doing this as this is not the primary purpose of the issue tracker. ### Help Us Help You On both websites, it is a good idea to structure your code and question in a way that is easy to read to entice people to answer it. For example, we encourage you to use syntax highlighting, indentation, and split text in paragraphs. Please keep in mind that people spend their free time trying to help you. You can make it easier for them if you provide versions of the relevant libraries and a runnable small project reproducing your issue. You can put your code on [JSBin](http://jsbin.com) or, for bigger projects, on GitHub. Make sure all the necessary dependencies are declared in `package.json` so anyone can run `npm install && npm start` and reproduce your issue. ## Development Visit the [issue tracker](https://github.com/airbnb/react-sketchapp/issues) to find a list of open issues that need attention. Fork, then clone the repo ```bash git clone https://github.com/your-username/react-sketchapp.git ``` ### Setting up your environment ### Testing, style & Linting To run tests ```bash npm run test ``` To run tests continuously ```bash npm run test:watch ``` This style of the codebase is enforced by [Prettier](https://prettier.io/). It is recommended that you install a Prettier plugin for your editor of choice when working on this codebase. ### Docs We always appreciate improvements to the documentation! #### Installing Gitbook To install the latest version of `gitbook` and prepare to build the documentation, run the following: ``` npm run docs:prepare ``` #### Building the Docs To build the documentation, run the following: ``` npm run docs:build ``` To watch and rebuild documentation when changes occur, run the following: ``` npm run docs:watch ``` The docs will be served at http://localhost:4000. #### Publishing the Docs To publish the documentation, run the following: ``` npm run docs:publish ``` ### Sending a Pull Request For non-trivial changes, please open an issue with a proposal for a new feature or refactoring before starting work — we don't want you to waste your time on a pull request that won't be accepted. On the other hand, sometimes the best way to start a discussion _is_ to send a pull request. Use your judgement! In general, the contribution workflow looks like this: - Open a new issue in the [Issue tracker](https://github.com/airbnb/react-sketchapp/issues). - Fork the repo. - Create a new feature branch based off the `master` branch. - Make sure all tests pass. - Submit a pull request, referencing any issues it addresses. Please try to keep your pull request focused in scope and avoid including unrelated commits. After you have submitted your pull request, we'll try to get back to you as soon as possible. We may suggest some changes or improvements. Thank you for contributing! ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ 👋 Hello! Thanks for contributing. Please use the template that matches your intention _I am..._ | ------------------------------------------------------------------------------------------------- | Requesting a new feature | ------------------------------------------------------------------------------------------------- **Proposal/Feature-request:** | ------------------------------------------------------------------------------------------------- | Reporting a bug or issue | ------------------------------------------------------------------------------------------------- **Expected behavior:** **Observed behavior:** **How to reproduce:** **Sketch version:** **Please attach screenshots, a zip file of your project, and/or a link to your github project** ================================================ FILE: .gitignore ================================================ # gitignore coverage/ .DS_Store *.log* node_modules lib react-example.sketchplugin _book .vscode # only apps should have lockfiles yarn.lock package-lock.json npm-shrinkwrap.json ================================================ FILE: .npmignore ================================================ .DS_Store *.log* node_modules _book examples docs .vscode yarn.lock __tests__ .github template book.json prettier.config.js .editorconfig .bookignore src jest.config.js tsconfig.json tsconfig.module.json .travis.yml ================================================ FILE: .travis.yml ================================================ os: osx language: node_js cache: directories: - node_modules # - $HOME/Library/Caches/Homebrew notifications: email: false node_js: - 'lts/*' # before_install: # - brew update # - brew cask install sketch # install Sketch # - mkdir -p "~/Library/Application Support/com.bohemiancoding.sketch3/Plugins" # create plugins folder # - echo $SKETCH_LICENSE > "~/Library/Application Support/com.bohemiancoding.sketch3/.deployment" # add the Sketch license before_script: - npm prune script: - npm run test:ci # - npm run test:e2e -- --app=/Applications/Sketch.app # after_script: # - rm "~/Library/App Support/com.bohemiancoding.sketch3/.deployment" # remove the Sketch license branches: except: - /^v\d+\.\d+\.\d+$/ ================================================ FILE: CHANGELOG.md ================================================ # Change Log This project adheres to [Semantic Versioning](http://semver.org/). Every release, along with the migration instructions, is documented on the Github [Releases](https://github.com/airbnb/react-sketchapp/releases) page. ## Version 3.2.6 - Fix the SVG component export ## Version 3.2.5 - Fix Skpm taking the wrong entry point when requiring react-sketchapp ## Version 3.2.4 - Fix the generated ES package ## Version 3.2.3 - Fix getting the font name (#510) ## Version 3.2.2 - Fix getting the default bridge on NodeJS ## Version 3.2.1 - `Platform.version` now reflects the Sketch version - Fix a bug for a broken version of `@sketch-hq/sketch-file-format-ts` ## Version 3.2.0 - Add a new `useWindowDimensions` hook for Artboard viewport (#501) ## Version 3.1.3 - Add proptypes for Text - Allow `fontWeigth` to be a number ## Version 3.1.2 - Handle passing a Sketch document more properly ## Version 3.1.1 - Fix for Sketch 64 ## Version 3.1.0 - Fix acceptable text children (#474) - Fix parsing of SVG arc shorthand parameters (#467) - Change default font resolution, always falling back to the system font when the `fontFamily` is missing or not specified ## Version 3.0.5 - Fix missing dependency (#462) ## Version 3.0.4 - Fix rendering images (#458) ## Version 3.0.3 - Fix typo in Symbol (Thanks @antoni!) - Fix messed up `js-sha` import (#456) ## Version 3.0.2 - Fix rotation direction (#433) - Fix Svg renders when the shape doesn't fit the viewbox (#288) - Add missing strokeAlignment prop (#276) ## Version 3.0.1 - Allow passing a style object when making a symbol - Expose `getSymbolMasterByName` ## Version 3.0.0 - Export Svg components in the Svg/index.js file (Thanks @saschazar21!) - Fix setting the overflow - The symbol masters will try to maintain their overrides IDs so as not to reset instances that have overrides - Improve error messages when trying to render a broken override - Do not crash if there is no source for an Image, we will just show an placeholder for the image - Handle specifying document in injectSymbols (#388) - Add support for paragraph spacing (#382 - Thanks @lessthanzero!) - `Image` and `Text` now support multiple shadows just like `View` - Add support for `TextShadow` - Add support for `transform` - Add support for running `react-sketchapp` on NodeJS using `renderToJSON()` - Port to TypeScript and publish TypeScript definitions - `TextStyles.get(name)` now returns text styles that are part of the document (even if they haven't been defined with `react-sketchapp`) (#407) - `getSymbolComponentByName` now returns Symbols that are part of the document (even if they haven't been defined with `react-sketchapp`) (#177) - Switch the order of the `TextStyles.create` arguments to `TextStyles.create(styles, options)` ## Version 3.0.0-beta.9 - Fix setting the overflow - The symbol masters will try to maintain their overrides IDs so as not to reset instances that have overrides - Improve error messages when trying to render a broken override - Export Svg components in the Svg/index.js file (Thanks @saschazar21!) ## Version 3.0.0-beta.8 - Flatten styles in exported Svg component (Thanks @dabbott!) ## Version 3.0.0-beta.7 - Add Node.js SVG renderer (Thanks @dabbott!) ## Version 3.0.0-beta.6 - Do not crash if there is no source for an Image, we will just show an placeholder for the image ## Version 3.0.0-beta.3 to 3.0.0-beta.5 - Fix setting overrides (#409) - Fix images on NodeJS - Fix Border-radius clipping incorrectly calculated (#279) ## Version 3.0.0-beta.1 - Fix ShapeGroup on nodejs (#387) - Handle specifying document in injectSymbols (#388) - Fix support for paragraph spacing on sketch >= 49 (#390) ## Version 3.0.0-beta.0 - Add support for paragraph spacing (#382 - Thanks @lessthanzero!) - `Image` and `Text` now support multiple shadows just like `View` - add support for `TextShadow` - Experimental support for `transform` - Experimental support for running `react-sketchapp` on NodeJS ## Version 2.1.0 - Ensure `makeSymbol` does not change currentPage (#353 - Thanks @jaridmargolin!) - Fix Text decoration underline style (#370 - Thanks @thecalvinchan!) - Add possibility to add multiple shadows and shadow spread (#277 - Thanks @ludwigfrank and @thierryc!) - Support rendering into wrapped object (hence support the new Sketch API) (#379) ## Version 2.0.0 - Now throws if the "Symbols" page is explicitly passed in as the `container` on the `render` method. Previously if you explicitly passed in the "Symbols" pages as a container, it would create a new page and render onto that. (#297 - Thanks @jaridmargolin!) - Now throws an error if you attempt to render a Document component into a node intended to be a child of `Document`. (#297 - Thanks @jaridmargolin!) - Adds support for rendering a `Page` component into a container passed through the `render` method. This allows for rendering multiple `Artboard`s onto an existing page. (#297 - Thanks @jaridmargolin!) - More predictable rendering of `RedBox`. (#297 - Thanks @jaridmargolin!) - Fix Symbols overrides for Sketch >= 46 (#198 - Thanks @ianhook!) - Fix text overrides when the name of the Text layer is not explicitly defined (#292 - Thanks @jaridmargolin!) - update `yoga-node` to 1.9 (#314) - Add support for Sketch 50 (#290) - Fix shared text style matching (#290) - Remove n^2 rendering problem with large symbol sets (#235 - Thanks @ianhook!) - `Page` without a name explicitly set will be auto-incremented ("Page 1", "Page 2", etc.) just like how Sketch is doing by default (#296 - Thanks @jaridmargolin!) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Airbnb Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================
react-sketchapp
render React components to Sketch; tailor-made for design systems
## Quick-start 🏃‍ First, make sure you have installed [Sketch](http://sketch.com) version 50+, & a recent [npm](https://nodejs.org/en/download/). Open a new Sketch file, then in a terminal: ```bash git clone https://github.com/airbnb/react-sketchapp.git cd react-sketchapp/examples/basic-setup && npm install npm run render ``` Next, [check out some more examples](https://github.com/airbnb/react-sketchapp/tree/master/examples)! ![readme-intro](https://cloud.githubusercontent.com/assets/591643/24777148/e742cd0e-1ad8-11e7-8751-090f6b2db514.png) [![npm](https://img.shields.io/npm/v/react-sketchapp.svg)](https://www.npmjs.com/package/react-sketchapp) ![Sketch.app](https://img.shields.io/badge/Sketch.app-43--50-brightgreen.svg) [![Travis](https://img.shields.io/travis/rust-lang/rust.svg)](https://travis-ci.org/airbnb/react-sketchapp) ## Why?! Managing the assets of design systems in Sketch is complex, error-prone and time consuming. Sketch is scriptable, but the API often changes. React provides the perfect wrapper to build reusable documents in a way already familiar to JavaScript developers. ## What does the code look like? ```js import * as React from 'react'; import { render, Text, Artboard } from 'react-sketchapp'; const App = props => ( {props.message} ); export default context => { render(, context.document.currentPage()); }; ``` ## What can I do with it? - **Manage design systems—** `react-sketchapp` was built for [Airbnb’s design system](http://airbnb.design/building-a-visual-language/); this is the easiest way to manage Sketch assets in a large design system - **Use real components for designs—** Implement your designs in code as React components and render them into Sketch - **Design with real data—** Designing with data is important but challenging; `react-sketchapp` makes it simple to fetch and incorporate real data into your Sketch files - **Build new tools on top of Sketch—** the easiest way to use Sketch as a canvas for custom design tooling Found a novel use? We'd love to hear about it! [Read more about why we built it](http://airbnb.design/painting-with-code/) ## Documentation - [Examples](http://airbnb.io/react-sketchapp/docs/examples.html) - [API Reference](http://airbnb.io/react-sketchapp/docs/API.html) - [Styling](http://airbnb.io/react-sketchapp/docs/guides/styling.html) - [Universal Rendering](http://airbnb.io/react-sketchapp/docs/guides/universal-rendering.html) - [Data Fetching](http://airbnb.io/react-sketchapp/docs/guides/data-fetching.html) - [FAQ](http://airbnb.io/react-sketchapp/docs/FAQ.html) - [Contributing](https://github.com/airbnb/react-sketchapp/blob/master/.github/CONTRIBUTING.md) ================================================ FILE: __tests__/jest/components/Artboard.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { Artboard } from '../../../src/components/Artboard'; import { StyleSheet } from '../../../src/stylesheet'; describe('', () => { it('renders children', () => { const tree = renderer.create(foo).toJSON(); expect(tree).toMatchSnapshot(); }); it.todo('flattens its stylesheet'); describe('name', () => { it('passes its name', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('defaults to Artboard', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); describe('style', () => { const styles = StyleSheet.create({ view: { flex: 1, }, }); it('accepts a plain object', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts a StyleSheet ordinal', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts an array of plain objects and/or StyleSheet ordinals', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts artboard viewport preset', () => { const tree = renderer .create( , ) .toJSON(); expect(tree).toMatchSnapshot(); }); }); }); ================================================ FILE: __tests__/jest/components/Document.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { Document } from '../../../src/components/Document'; describe('', () => { it('renders children', () => { const tree = renderer.create(foo).toJSON(); expect(tree).toMatchSnapshot(); }); }); ================================================ FILE: __tests__/jest/components/Image.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { Image } from '../../../src/components/Image'; import { StyleSheet } from '../../../src/stylesheet'; describe('', () => { it('renders children', () => { const tree = renderer.create(foo).toJSON(); expect(tree).toMatchSnapshot(); }); it.todo('flattens its stylesheet'); describe('name', () => { it('passes its name', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('defaults to Image', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); describe('resizeMode', () => { it('translates contain', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('translates cover', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('translates stretch', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('translates center', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('translates repeat', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('translates none', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('falls back to cover', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('prefers prop to style', () => { const tree = renderer .create() .toJSON(); expect(tree).toMatchSnapshot(); }); it('falls back to a resizeMode from style', () => { const tree = renderer .create() .toJSON(); expect(tree).toMatchSnapshot(); }); }); describe('source', () => { it('prefers source over defaultSource', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('falls back to defaultSource if available', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('sets height from source', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('sets width from source', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('favors style over source for height', () => { const tree = renderer .create() .toJSON(); expect(tree).toMatchSnapshot(); }); it('favors style over source for width', () => { const tree = renderer .create() .toJSON(); expect(tree).toMatchSnapshot(); }); }); describe('style', () => { const styles = StyleSheet.create({ view: { flex: 1, }, }); it('accepts a plain object', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts a StyleSheet ordinal', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts an array of plain objects and/or StyleSheet ordinals', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); }); ================================================ FILE: __tests__/jest/components/Page.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { Page } from '../../../src/components/Page'; describe('', () => { it('renders children', () => { const tree = renderer.create(foo).toJSON(); expect(tree).toMatchSnapshot(); }); describe('name', () => { it('passes its name', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('passes its name and avoids Symbol page conflict', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('passes otherProps', () => { // @ts-ignore const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); }); ================================================ FILE: __tests__/jest/components/RedBox.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { RedBox } from '../../../src/components/RedBox'; describe('', () => { it('renders simple errors', () => { const mockedError = new Error('THIS IS AN ERROR'); // override stack trace so that it's constant accross node versions mockedError.stack = `Error: awdawd at repl:1:13 at Script.runInThisContext (vm.js:65:33) at REPLServer.defaultEval (repl.js:248:29) at bound (domain.js:375:14) at REPLServer.runBound [as eval] (domain.js:388:12) at REPLServer.onLine (repl.js:501:10) at REPLServer.emit (events.js:185:15) at REPLServer.emit (domain.js:421:20) at REPLServer.Interface._onLine (readline.js:285:10) at REPLServer.Interface._line (readline.js:638:8)`; const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('renders string errors', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); ================================================ FILE: __tests__/jest/components/Svg.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import Svg, { G, Path } from '../../../src/components/Svg'; describe('', () => { it('passes its children', () => { const tree = renderer .create( , ) .toJSON(); expect(tree).toMatchSnapshot(); }); it('also works when child is directly imported', () => { const tree = renderer.create( , ); expect(tree.toJSON()).toMatchSnapshot(); }); }); ================================================ FILE: __tests__/jest/components/Text.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { Text } from '../../../src/components/Text'; import { StyleSheet } from '../../../src/stylesheet'; describe('', () => { it('passes its children', () => { const tree = renderer.create(foo).toJSON(); expect(tree).toMatchSnapshot(); }); describe('name', () => { it('passes its name', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('defaults to Text', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); describe('style', () => { const styles = StyleSheet.create({ view: { flex: 1, }, }); it('accepts a plain object', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts a StyleSheet ordinal', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts an array of plain objects and/or StyleSheet ordinals', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); }); ================================================ FILE: __tests__/jest/components/View.tsx ================================================ import * as React from 'react'; import * as renderer from 'react-test-renderer'; import { View } from '../../../src/components/View'; import { StyleSheet } from '../../../src/stylesheet'; describe('', () => { it('passes its children', () => { const tree = renderer.create(foo).toJSON(); expect(tree).toMatchSnapshot(); }); describe('name', () => { it('passes its name', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('defaults to View', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); describe('style', () => { const styles = StyleSheet.create({ view: { flex: 1, }, }); it('accepts a plain object', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts a StyleSheet ordinal', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); it('accepts an array of plain objects and/or StyleSheet ordinals', () => { const tree = renderer.create().toJSON(); expect(tree).toMatchSnapshot(); }); }); }); ================================================ FILE: __tests__/jest/components/__snapshots__/Artboard.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` name defaults to Artboard 1`] = ` `; exports[` name passes its name 1`] = ` `; exports[` renders children 1`] = ` foo `; exports[` style accepts a StyleSheet ordinal 1`] = ` `; exports[` style accepts a plain object 1`] = ` `; exports[` style accepts an array of plain objects and/or StyleSheet ordinals 1`] = ` `; exports[` style accepts artboard viewport preset 1`] = ` `; ================================================ FILE: __tests__/jest/components/__snapshots__/Document.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` renders children 1`] = ` foo `; ================================================ FILE: __tests__/jest/components/__snapshots__/Image.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` name defaults to Image 1`] = ` `; exports[` name passes its name 1`] = ` `; exports[` renders children 1`] = ` foo `; exports[` resizeMode falls back to a resizeMode from style 1`] = ` `; exports[` resizeMode falls back to cover 1`] = ` `; exports[` resizeMode prefers prop to style 1`] = ` `; exports[` resizeMode translates center 1`] = ` `; exports[` resizeMode translates contain 1`] = ` `; exports[` resizeMode translates cover 1`] = ` `; exports[` resizeMode translates none 1`] = ` `; exports[` resizeMode translates repeat 1`] = ` `; exports[` resizeMode translates stretch 1`] = ` `; exports[` source falls back to defaultSource if available 1`] = ` `; exports[` source favors style over source for height 1`] = ` `; exports[` source favors style over source for width 1`] = ` `; exports[` source prefers source over defaultSource 1`] = ` `; exports[` source sets height from source 1`] = ` `; exports[` source sets width from source 1`] = ` `; exports[` style accepts a StyleSheet ordinal 1`] = ` `; exports[` style accepts a plain object 1`] = ` `; exports[` style accepts an array of plain objects and/or StyleSheet ordinals 1`] = ` `; ================================================ FILE: __tests__/jest/components/__snapshots__/Page.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` name passes its name 1`] = ` `; exports[` name passes its name and avoids Symbol page conflict 1`] = ` `; exports[` name passes otherProps 1`] = ` `; exports[` renders children 1`] = ` foo `; ================================================ FILE: __tests__/jest/components/__snapshots__/RedBox.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` renders simple errors 1`] = ` Error: THIS IS AN ERROR Script.runInThisContext REPLServer.defaultEval bound REPLServer.runBound [as eval] REPLServer.onLine REPLServer.emit REPLServer.emit REPLServer.Interface._onLine REPLServer.Interface._line `; exports[` renders string errors 1`] = ` Error: String only error `; ================================================ FILE: __tests__/jest/components/__snapshots__/Svg.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` also works when child is directly imported 1`] = ` `; exports[` passes its children 1`] = ` `; ================================================ FILE: __tests__/jest/components/__snapshots__/Text.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` name defaults to Text 1`] = ``; exports[` name passes its name 1`] = ` `; exports[` passes its children 1`] = ` foo `; exports[` style accepts a StyleSheet ordinal 1`] = ` `; exports[` style accepts a plain object 1`] = ` `; exports[` style accepts an array of plain objects and/or StyleSheet ordinals 1`] = ` `; ================================================ FILE: __tests__/jest/components/__snapshots__/View.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` name defaults to View 1`] = ` `; exports[` name passes its name 1`] = ` `; exports[` passes its children 1`] = ` foo `; exports[` style accepts a StyleSheet ordinal 1`] = ` `; exports[` style accepts a plain object 1`] = ` `; exports[` style accepts an array of plain objects and/or StyleSheet ordinals 1`] = ` `; ================================================ FILE: __tests__/jest/components/nodeImpl/Svg.tsx ================================================ import * as React from 'react'; import * as ReactSketch from '../../../../src'; import Svg from '../../../../src/components/Svg'; jest.mock('../../../../src/jsonUtils/models', () => ({ ...require.requireActual('../../../../src/jsonUtils/models'), generateID: jest.fn((seed) => (seed ? `${seed}mockID` : 'mockID')), })); describe('node ', () => { it('generates the json for an svg', () => { class SVGElement extends React.Component { render() { return ( ); } } expect(ReactSketch.renderToJSON()).toMatchSnapshot(); }); }); ================================================ FILE: __tests__/jest/components/nodeImpl/__snapshots__/Svg.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`node generates the json for an svg 1`] = ` Object { "_class": "group", "booleanOperation": -1, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 447, "width": 494, "x": 0, "y": 0, }, "hasClickThrough": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 2, "layers": Array [ Object { "_class": "group", "booleanOperation": -1, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 447, "width": 494, "x": 0, "y": 0, }, "hasClickThrough": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 2, "layers": Array [ Object { "_class": "shapeGroup", "booleanOperation": -1, "clippingMaskMode": 0, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 447, "width": 494, "x": 0, "y": 0, }, "hasClickThrough": false, "hasClippingMask": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "layers": Array [ Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 447, "width": 494, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 1}", "curveMode": 1, "curveTo": "{0.5, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0, 0.3579418344519016}", "curveMode": 1, "curveTo": "{0, 0.3579418344519016}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0, 0.3579418344519016}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.2165991902834008, 0.03355704697986577}", "curveMode": 1, "curveTo": "{0.2165991902834008, 0.03355704697986577}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.2165991902834008, 0.03355704697986577}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 0}", "curveMode": 1, "curveTo": "{0.5, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 0}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.7834008097165992, 0.03355704697986577}", "curveMode": 1, "curveTo": "{0.7834008097165992, 0.03355704697986577}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.7834008097165992, 0.03355704697986577}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{1, 0.3579418344519016}", "curveMode": 1, "curveTo": "{1, 0.3579418344519016}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{1, 0.3579418344519016}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, ], "name": "ShapeGroup", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "endMarkerType": 0, "fills": Array [ Object { "_class": "fill", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0.6823529411764706, "red": 1, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "fillType": 0, "gradient": Object { "_class": "gradient", "elipseLength": 0, "from": "{0.5, 0}", "gradientType": 0, "stops": Array [ Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 1, "green": 1, "red": 1, }, "position": 0, }, Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0, "red": 0, }, "position": 1, }, ], "to": "{0.5, 1}", }, "isEnabled": true, "noiseIndex": 0, "noiseIntensity": 0, "patternFillType": 1, "patternTileScale": 1, }, ], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, "windingRule": 1, }, Object { "_class": "shapeGroup", "booleanOperation": -1, "clippingMaskMode": 0, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 287, "width": 494, "x": 0, "y": 160, }, "hasClickThrough": false, "hasClippingMask": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "layers": Array [ Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 287, "width": 494, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 1}", "curveMode": 1, "curveTo": "{0.5, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0, 0}", "curveMode": 1, "curveTo": "{0, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0, 0}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{1, 0}", "curveMode": 1, "curveTo": "{1, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{1, 0}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, ], "name": "ShapeGroup", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "endMarkerType": 0, "fills": Array [ Object { "_class": "fill", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0.4235294117647059, "red": 0.9254901960784314, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "fillType": 0, "gradient": Object { "_class": "gradient", "elipseLength": 0, "from": "{0.5, 0}", "gradientType": 0, "stops": Array [ Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 1, "green": 1, "red": 1, }, "position": 0, }, Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0, "red": 0, }, "position": 1, }, ], "to": "{0.5, 1}", }, "isEnabled": true, "noiseIndex": 0, "noiseIntensity": 0, "patternFillType": 1, "patternTileScale": 1, }, ], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, "windingRule": 1, }, Object { "_class": "shapeGroup", "booleanOperation": -1, "clippingMaskMode": 0, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 287, "width": 294, "x": 100, "y": 160, }, "hasClickThrough": false, "hasClippingMask": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "layers": Array [ Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 287, "width": 294, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 1}", "curveMode": 1, "curveTo": "{0.5, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0, 0}", "curveMode": 1, "curveTo": "{0, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0, 0}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{1, 0}", "curveMode": 1, "curveTo": "{1, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{1, 0}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, ], "name": "ShapeGroup", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "endMarkerType": 0, "fills": Array [ Object { "_class": "fill", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0.6823529411764706, "red": 1, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "fillType": 0, "gradient": Object { "_class": "gradient", "elipseLength": 0, "from": "{0.5, 0}", "gradientType": 0, "stops": Array [ Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 1, "green": 1, "red": 1, }, "position": 0, }, Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0, "red": 0, }, "position": 1, }, ], "to": "{0.5, 1}", }, "isEnabled": true, "noiseIndex": 0, "noiseIntensity": 0, "patternFillType": 1, "patternTileScale": 1, }, ], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, "windingRule": 1, }, Object { "_class": "shapeGroup", "booleanOperation": -1, "clippingMaskMode": 0, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 160, "width": 294, "x": 100, "y": 0, }, "hasClickThrough": false, "hasClippingMask": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "layers": Array [ Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 160, "width": 294, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 0}", "curveMode": 1, "curveTo": "{0.5, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 0}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0, 1}", "curveMode": 1, "curveTo": "{0, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{1, 1}", "curveMode": 1, "curveTo": "{1, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{1, 1}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, ], "name": "ShapeGroup", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "endMarkerType": 0, "fills": Array [ Object { "_class": "fill", "color": Object { "_class": "color", "alpha": 1, "blue": 0.7058823529411765, "green": 0.9372549019607843, "red": 1, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "fillType": 0, "gradient": Object { "_class": "gradient", "elipseLength": 0, "from": "{0.5, 0}", "gradientType": 0, "stops": Array [ Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 1, "green": 1, "red": 1, }, "position": 0, }, Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0, "red": 0, }, "position": 1, }, ], "to": "{0.5, 1}", }, "isEnabled": true, "noiseIndex": 0, "noiseIntensity": 0, "patternFillType": 1, "patternTileScale": 1, }, ], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, "windingRule": 1, }, Object { "_class": "shapeGroup", "booleanOperation": -1, "clippingMaskMode": 0, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 145, "width": 494, "x": 0, "y": 15, }, "hasClickThrough": false, "hasClippingMask": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "layers": Array [ Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 145, "width": 494, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.2165991902834008, 0}", "curveMode": 1, "curveTo": "{0.2165991902834008, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.2165991902834008, 0}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.10526315789473684, 0.503448275862069}", "curveMode": 1, "curveTo": "{0.10526315789473684, 0.503448275862069}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.10526315789473684, 0.503448275862069}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0, 1}", "curveMode": 1, "curveTo": "{0, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.20445344129554655, 1}", "curveMode": 1, "curveTo": "{0.20445344129554655, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.20445344129554655, 1}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 145, "width": 494, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.7834008097165992, 0}", "curveMode": 1, "curveTo": "{0.7834008097165992, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.7834008097165992, 0}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.8947368421052632, 0.503448275862069}", "curveMode": 1, "curveTo": "{0.8947368421052632, 0.503448275862069}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.8947368421052632, 0.503448275862069}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{1, 1}", "curveMode": 1, "curveTo": "{1, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{1, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.7955465587044535, 1}", "curveMode": 1, "curveTo": "{0.7955465587044535, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.7955465587044535, 1}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, ], "name": "ShapeGroup", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "endMarkerType": 0, "fills": Array [ Object { "_class": "fill", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0.6823529411764706, "red": 1, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "fillType": 0, "gradient": Object { "_class": "gradient", "elipseLength": 0, "from": "{0.5, 0}", "gradientType": 0, "stops": Array [ Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 1, "green": 1, "red": 1, }, "position": 0, }, Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0, "red": 0, }, "position": 1, }, ], "to": "{0.5, 1}", }, "isEnabled": true, "noiseIndex": 0, "noiseIntensity": 0, "patternFillType": 1, "patternTileScale": 1, }, ], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, "windingRule": 1, }, Object { "_class": "shapeGroup", "booleanOperation": -1, "clippingMaskMode": 0, "do_objectID": "mockID", "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 160, "width": 294, "x": 100, "y": 0, }, "hasClickThrough": false, "hasClippingMask": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "layers": Array [ Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 160, "width": 294, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.023809523809523808, 0.09375}", "curveMode": 1, "curveTo": "{0.023809523809523808, 0.09375}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.023809523809523808, 0.09375}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0, 1}", "curveMode": 1, "curveTo": "{0, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 0}", "curveMode": 1, "curveTo": "{0.5, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 0}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, Object { "_class": "shapePath", "booleanOperation": -1, "do_objectID": "mockID", "edited": false, "exportOptions": Object { "_class": "exportOptions", "exportFormats": Array [], "includedLayerIds": Array [], "layerOptions": 0, "shouldTrim": false, }, "frame": Object { "_class": "rect", "constrainProportions": false, "height": 160, "width": 294, "x": 0, "y": 0, }, "isClosed": false, "isFixedToViewport": false, "isFlippedHorizontal": false, "isFlippedVertical": false, "isLocked": false, "isVisible": true, "layerListExpandedType": 0, "name": "Path", "nameIsFixed": false, "pointRadiusBehaviour": 1, "points": Array [ Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.9761904761904762, 0.09375}", "curveMode": 1, "curveTo": "{0.9761904761904762, 0.09375}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.9761904761904762, 0.09375}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{1, 1}", "curveMode": 1, "curveTo": "{1, 1}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{1, 1}", }, Object { "_class": "curvePoint", "cornerRadius": 0, "curveFrom": "{0.5, 0}", "curveMode": 1, "curveTo": "{0.5, 0}", "hasCurveFrom": false, "hasCurveTo": false, "point": "{0.5, 0}", }, ], "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, }, ], "name": "ShapeGroup", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "endMarkerType": 0, "fills": Array [ Object { "_class": "fill", "color": Object { "_class": "color", "alpha": 1, "blue": 0.0196078431372549, "green": 0.8274509803921568, "red": 0.996078431372549, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "fillType": 0, "gradient": Object { "_class": "gradient", "elipseLength": 0, "from": "{0.5, 0}", "gradientType": 0, "stops": Array [ Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 1, "green": 1, "red": 1, }, "position": 0, }, Object { "_class": "gradientStop", "color": Object { "_class": "color", "alpha": 1, "blue": 0, "green": 0, "red": 0, }, "position": 1, }, ], "to": "{0.5, 1}", }, "isEnabled": true, "noiseIndex": 0, "noiseIntensity": 0, "patternFillType": 1, "patternTileScale": 1, }, ], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, "windingRule": 1, }, ], "name": "Shape", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "endMarkerType": 0, "fills": Array [], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, }, ], "name": "Svg", "nameIsFixed": false, "resizingConstraint": 63, "resizingType": 0, "rotation": 0, "shouldBreakMaskChain": false, "style": Object { "_class": "style", "borderOptions": Object { "_class": "borderOptions", "dashPattern": Array [], "isEnabled": false, "lineCapStyle": 0, "lineJoinStyle": 0, }, "colorControls": Object { "_class": "colorControls", "brightness": 1, "contrast": 1, "hue": 1, "isEnabled": false, "saturation": 1, }, "contextSettings": Object { "_class": "graphicsContextSettings", "blendMode": 0, "opacity": 1, }, "endMarkerType": 0, "fills": Array [], "innerShadows": Array [], "miterLimit": 10, "shadows": Array [], "startMarkerType": 0, "windingRule": 1, }, } `; ================================================ FILE: __tests__/jest/index.ts ================================================ import * as ReactSketch from '../../src'; describe('public API', () => { it('exports render', () => { expect(ReactSketch.render).toBeDefined(); }); it('exports renderToJSON', () => { expect(ReactSketch.renderToJSON).toBeDefined(); }); it('exports StyleSheet', () => { expect(ReactSketch.StyleSheet).toBeDefined(); }); it('exports Document', () => { expect(ReactSketch.Document).toBeDefined(); }); it('exports Page', () => { expect(ReactSketch.Page).toBeDefined(); }); it('exports Artboard', () => { expect(ReactSketch.Artboard).toBeDefined(); }); it('exports Image', () => { expect(ReactSketch.Image).toBeDefined(); }); it('exports RedBox', () => { expect(ReactSketch.RedBox).toBeDefined(); }); it('exports Text', () => { expect(ReactSketch.Text).toBeDefined(); }); it('exports TextStyles', () => { expect(ReactSketch.TextStyles).toBeDefined(); }); it('exports View', () => { expect(ReactSketch.View).toBeDefined(); }); it('exports Platform', () => { expect(ReactSketch.Platform).toBeDefined(); }); }); ================================================ FILE: __tests__/jest/jsonUtils/computeTextTree.ts ================================================ import { computeTextTree } from '../../../src/jsonUtils/computeTextTree'; import { Context } from '../../../src/utils/Context'; // Example Text component tree const treeStub = { type: 'text', props: { name: 'Swatch Hex', style: { color: '#636464', zIndex: 1, }, }, children: [ '#F3F4F4', ' ', { type: 'text', props: { name: 'Text', style: { color: 'blue', }, }, children: ['Hello World'], }, ], }; // Correct Output const treeFixture = [ { content: '#F3F4F4', textStyles: {} }, { content: ' ', textStyles: {} }, { content: 'Hello World', textStyles: { color: 'blue' } }, ]; describe('Compute Text Tree', () => { it('correctly handle Text nodes', () => { const tree = computeTextTree(treeStub, new Context()); expect(tree).toEqual(treeFixture); }); }); ================================================ FILE: __tests__/jest/jsonUtils/computeYogaNode.ts ================================================ import yoga from 'yoga-layout-prebuilt'; import { computeYogaNode } from '../../../src/jsonUtils/computeYogaNode'; import { Context } from '../../../src/utils/Context'; import bridge from '../../../src/platformBridges/macos'; const widthAndHeightStylesStub = { width: 10, height: 10, }; const widthAndHeightStylesStubFixture = { left: 0, right: 0, top: 0, bottom: 0, width: 10, height: 10, }; const createTreeNode = (style: { [key: string]: number | string }) => ({ type: 'foo', props: { style, }, children: [], }); const createYogaNodes = ( styles: Array<{ [key: string]: number | string }>, containerWidth?: number, containerHeight?: number, ) => { const yogaNodes = []; styles.forEach((style) => { const treeNode = createTreeNode(style); const ctx = new Context(); const { node } = computeYogaNode(bridge)(treeNode, ctx); node.calculateLayout( containerWidth || undefined, containerHeight || undefined, yoga.DIRECTION_LTR, ); yogaNodes.push(node.getComputedLayout()); }); return yogaNodes; }; describe('Compute Yoga Node', () => { it('correctly handles width: 0, auto, number', () => { const stylesToTest = [{ width: 100 }, { width: 0 }, { width: 'auto' }]; const [numberNode, noneNode, autoNode] = createYogaNodes(stylesToTest); expect(numberNode.width).toEqual(100); expect(noneNode.width).toEqual(0); expect(autoNode.width).toEqual(0); }); it('correctly handles height: 0, auto, number', () => { const stylesToTest = [{ height: 100 }, { height: 0 }, { height: 'auto' }]; const [numberNode, noneNode, autoNode] = createYogaNodes(stylesToTest); expect(numberNode.height).toEqual(100); expect(noneNode.height).toEqual(0); expect(autoNode.height).toEqual(0); }); it('correctly handles min-height: 0 & number', () => { const stylesToTest = [{ minHeight: 100 }, { minHeight: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode.height).toEqual(100); expect(noneNode.height).toEqual(0); }); it('correctly handles max-height: 0 & number', () => { const stylesToTest = [{ height: '100%', maxHeight: 100 }, { maxHeight: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest, 500, 500); expect(numberNode.height).toEqual(100); expect(noneNode.height).toEqual(0); }); it('correctly handles min-width: 0 & number', () => { const stylesToTest = [{ minWidth: 100 }, { minWidth: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode.width).toEqual(100); expect(noneNode.width).toEqual(0); }); it('correctly handles max-width: 0 & number', () => { const stylesToTest = [{ width: '100%', maxWidth: 100 }, { maxWidth: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest, 500, 500); expect(numberNode.width).toEqual(100); expect(noneNode.width).toEqual(0); }); it('correctly handles margin', () => { const stylesToTest = [{ margin: 100 }, { margin: 0 }, { margin: 'auto' }]; const [numberNode, noneNode, autoNode] = createYogaNodes(stylesToTest); expect(numberNode).toEqual({ left: 100, right: 100, top: 100, bottom: 100, width: 0, height: 0, }); expect(noneNode).toEqual({ left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0, }); expect(autoNode).toEqual({ left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0, }); }); it('correctly handles padding', () => { const stylesToTest = [{ padding: 100 }, { padding: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode).toEqual({ left: 0, right: 0, top: 0, bottom: 0, width: 200, height: 200, }); expect(noneNode).toEqual({ left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0, }); }); it('correctly handles border', () => { const stylesToTest = [{ borderWidth: 10 }, { borderWidth: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode).toEqual({ left: 0, right: 0, top: 0, bottom: 0, width: 20, height: 20, }); expect(noneNode).toEqual({ left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0, }); }); it('correctly handles flex: 0, number', () => { const stylesToTest = [{ flex: 1 }, { flex: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode.width).toEqual(0); expect(noneNode.width).toEqual(0); }); it('correctly handles flexGrow: 0, number', () => { const stylesToTest = [{ flexGrow: 1 }, { flexGrow: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode.width).toEqual(0); expect(noneNode.width).toEqual(0); }); it('correctly handles flexShrink: 0, number', () => { const stylesToTest = [{ flexShrink: 1 }, { flexShrink: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode.width).toEqual(0); expect(noneNode.width).toEqual(0); }); it('correctly handles flexBasis: 0, number', () => { const stylesToTest = [{ flexBasis: 1 }, { flexBasis: 0 }]; const [numberNode, noneNode] = createYogaNodes(stylesToTest); expect(numberNode.width).toEqual(0); expect(noneNode.width).toEqual(0); }); it('correctly handles position: relative & absolute', () => { const stylesToTest = [ { position: 'relative', left: 10 }, { position: 'absolute', top: 10 }, ]; const [relativeNode, absoluteNode] = createYogaNodes(stylesToTest); expect(relativeNode).toEqual({ left: 10, right: 10, top: 0, bottom: 0, width: 0, height: 0, }); expect(absoluteNode).toEqual({ left: 0, right: 0, top: 10, bottom: 10, width: 0, height: 0, }); }); it('correctly handles display: flex & none', () => { const stylesToTest = [ { display: 'flex', ...widthAndHeightStylesStub }, { display: 'none', width: 10, height: 10 }, ]; const [relativeNode, absoluteNode] = createYogaNodes(stylesToTest); expect(relativeNode).toEqual(widthAndHeightStylesStubFixture); expect(absoluteNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles overflow: visible, scroll, hidden', () => { const stylesToTest = [ { overflow: 'visible', ...widthAndHeightStylesStub }, { overflow: 'scroll', ...widthAndHeightStylesStub }, { overflow: 'hidden', ...widthAndHeightStylesStub }, ]; const [visibleNode, scrollNode, hiddenNode] = createYogaNodes(stylesToTest); expect(visibleNode).toEqual(widthAndHeightStylesStubFixture); expect(scrollNode).toEqual(widthAndHeightStylesStubFixture); expect(hiddenNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles flexDirection', () => { const stylesToTest = [ { flexDirection: 'row', ...widthAndHeightStylesStub }, { flexDirection: 'column', ...widthAndHeightStylesStub }, { flexDirection: 'row-reverse', ...widthAndHeightStylesStub }, { flexDirection: 'column-reverse', ...widthAndHeightStylesStub }, ]; const [rowNode, colNode, rowReverseNode, colReverseNode] = createYogaNodes(stylesToTest); expect(rowNode).toEqual(widthAndHeightStylesStubFixture); expect(colNode).toEqual(widthAndHeightStylesStubFixture); expect(rowReverseNode).toEqual(widthAndHeightStylesStubFixture); expect(colReverseNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles justifyContent', () => { const stylesToTest = [ { justifyContent: 'flex-start', ...widthAndHeightStylesStub }, { justifyContent: 'flex-end', ...widthAndHeightStylesStub }, { justifyContent: 'center', ...widthAndHeightStylesStub }, { justifyContent: 'space-between', ...widthAndHeightStylesStub }, { justifyContent: 'space-around', ...widthAndHeightStylesStub }, ]; const [startNode, endNode, centerNode, spaceBetweenNode, spaceAroundNode] = createYogaNodes( stylesToTest, ); expect(startNode).toEqual(widthAndHeightStylesStubFixture); expect(endNode).toEqual(widthAndHeightStylesStubFixture); expect(centerNode).toEqual(widthAndHeightStylesStubFixture); expect(spaceBetweenNode).toEqual(widthAndHeightStylesStubFixture); expect(spaceAroundNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles alignContent', () => { const stylesToTest = [ { alignContent: 'flex-start', ...widthAndHeightStylesStub }, { alignContent: 'flex-end', ...widthAndHeightStylesStub }, { alignContent: 'center', ...widthAndHeightStylesStub }, { alignContent: 'stretch', ...widthAndHeightStylesStub }, { alignContent: 'baseline', ...widthAndHeightStylesStub }, { alignContent: 'space-between', ...widthAndHeightStylesStub }, { alignContent: 'space-around', ...widthAndHeightStylesStub }, { alignContent: 'auto', ...widthAndHeightStylesStub }, ]; const [ startNode, endNode, centerNode, stretchNode, baselineNode, spaceBetweenNode, spaceAroundNode, autoNode, ] = createYogaNodes(stylesToTest); expect(startNode).toEqual(widthAndHeightStylesStubFixture); expect(endNode).toEqual(widthAndHeightStylesStubFixture); expect(centerNode).toEqual(widthAndHeightStylesStubFixture); expect(stretchNode).toEqual(widthAndHeightStylesStubFixture); expect(baselineNode).toEqual(widthAndHeightStylesStubFixture); expect(spaceBetweenNode).toEqual(widthAndHeightStylesStubFixture); expect(spaceAroundNode).toEqual(widthAndHeightStylesStubFixture); expect(autoNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles alignItems', () => { const stylesToTest = [ { alignItems: 'flex-start', ...widthAndHeightStylesStub }, { alignItems: 'flex-end', ...widthAndHeightStylesStub }, { alignItems: 'center', ...widthAndHeightStylesStub }, { alignItems: 'stretch', ...widthAndHeightStylesStub }, { alignItems: 'baseline', ...widthAndHeightStylesStub }, ]; const [startNode, endNode, centerNode, stretchNode, baselineNode] = createYogaNodes( stylesToTest, ); expect(startNode).toEqual(widthAndHeightStylesStubFixture); expect(endNode).toEqual(widthAndHeightStylesStubFixture); expect(centerNode).toEqual(widthAndHeightStylesStubFixture); expect(stretchNode).toEqual(widthAndHeightStylesStubFixture); expect(baselineNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles alignSelf', () => { const stylesToTest = [ { alignSelf: 'flex-start', ...widthAndHeightStylesStub }, { alignSelf: 'flex-end', ...widthAndHeightStylesStub }, { alignSelf: 'center', ...widthAndHeightStylesStub }, { alignSelf: 'stretch', ...widthAndHeightStylesStub }, { alignSelf: 'baseline', ...widthAndHeightStylesStub }, ]; const [startNode, endNode, centerNode, stretchNode, baselineNode] = createYogaNodes( stylesToTest, ); expect(startNode).toEqual(widthAndHeightStylesStubFixture); expect(endNode).toEqual(widthAndHeightStylesStubFixture); expect(centerNode).toEqual(widthAndHeightStylesStubFixture); expect(stretchNode).toEqual(widthAndHeightStylesStubFixture); expect(baselineNode).toEqual(widthAndHeightStylesStubFixture); }); it('correctly handles flexWrap', () => { const stylesToTest = [ { flexWrap: 'no-wrap', ...widthAndHeightStylesStub }, { flexWrap: 'wrap', ...widthAndHeightStylesStub }, { flexWrap: 'wrap-reverse', ...widthAndHeightStylesStub }, ]; const [noWrapNode, wrapNode, wrapReverseNode] = createYogaNodes(stylesToTest); expect(noWrapNode).toEqual(widthAndHeightStylesStubFixture); expect(wrapNode).toEqual(widthAndHeightStylesStubFixture); expect(wrapReverseNode).toEqual(widthAndHeightStylesStubFixture); }); }); ================================================ FILE: __tests__/jest/jsonUtils/computeYogaTree.ts ================================================ import yoga from 'yoga-layout-prebuilt'; import { computeYogaTree } from '../../../src/jsonUtils/computeYogaTree'; import { Context } from '../../../src/utils/Context'; import bridge from '../../../src/platformBridges/macos'; const treeRootStub = { type: 'artboard', props: { style: { flexDirection: 'row', flexWrap: 'wrap', width: 416, }, name: 'Swatches', }, children: [ { type: 'view', props: { name: 'Swatch Haus', style: { backgroundColor: '#F3F4F4', height: 96, marginTop: 4, marginRight: 4, marginBottom: 4, marginLeft: 4, paddingTop: 8, paddingRight: 8, paddingBottom: 8, paddingLeft: 8, width: 96, }, }, children: [], }, ], }; computeYogaTree(bridge)(treeRootStub, new Context()); describe('Compute Yoga Tree', () => { it('correctly create yoga nodes into layout tree', () => { const yogaTree = computeYogaTree(bridge)(treeRootStub, new Context()); yogaTree.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR); expect(yogaTree.getComputedLayout()).toEqual({ bottom: 0, height: 104, left: 0, right: 0, top: 0, width: 416, }); expect(yogaTree.getChild(0).getComputedLayout()).toEqual({ bottom: 4, height: 96, left: 4, right: 4, top: 4, width: 96, }); }); }); ================================================ FILE: __tests__/jest/jsonUtils/layerGroup.ts ================================================ import { layerGroup } from '../../../src/jsonUtils/layerGroup'; describe('layer group', () => { it('is correctly constructed', () => { const group = layerGroup(100, 200, 300, 400, 0.5); expect(group).toHaveProperty('frame.x', 100); expect(group).toHaveProperty('frame.y', 200); expect(group).toHaveProperty('frame.width', 300); expect(group).toHaveProperty('frame.height', 400); expect(group).toHaveProperty('style.contextSettings.opacity', 0.5); }); }); ================================================ FILE: __tests__/jest/jsonUtils/models.ts ================================================ import { generateID, makeColorFromCSS, makeColorFill, makeRect, makeSymbolInstance, makeSymbolMaster, } from '../../../src/jsonUtils/models'; describe('generateID', () => { it('is unique', () => { expect(generateID()).not.toBe(generateID()); }); it('seed generates different ID', () => { expect(generateID('test')).not.toBe(generateID('test')); }); it('hardcoded seed generates same ID', () => { expect(generateID('test', true)).toBe(generateID('test', true)); }); }); const BLACK = { _class: 'color', red: 0, green: 0, blue: 0, alpha: 1, }; const WHITE = { _class: 'color', red: 1, green: 1, blue: 1, alpha: 1, }; const GOLD = { _class: 'color', red: 0.8745098039215686, green: 0.7294117647058823, blue: 0.4117647058823529, alpha: 1, }; const PURPLE = { _class: 'color', red: 0.4, green: 0.2, blue: 0.6, alpha: 1, }; describe('makeColorFromCSS', () => { it('works with hex colors', () => { expect(makeColorFromCSS('#000')).toEqual(BLACK); expect(makeColorFromCSS('#000000')).toEqual(BLACK); expect(makeColorFromCSS('#FFF')).toEqual(WHITE); expect(makeColorFromCSS('#FFFFFF')).toEqual(WHITE); expect(makeColorFromCSS('#DFBA69')).toEqual(GOLD); }); it('works with named colors', () => { expect(makeColorFromCSS('black')).toEqual(BLACK); expect(makeColorFromCSS('white')).toEqual(WHITE); expect(makeColorFromCSS('rebeccapurple')).toEqual(PURPLE); }); it('is case-insensitive', () => { expect(makeColorFromCSS('BLACK')).toEqual(BLACK); expect(makeColorFromCSS('wHIte')).toEqual(WHITE); expect(makeColorFromCSS('rebeccaPurple')).toEqual(PURPLE); }); it('works with rgb colors', () => { expect(makeColorFromCSS('rgb(0, 0, 0)')).toEqual(BLACK); expect(makeColorFromCSS('rgb(255, 255, 255)')).toEqual(WHITE); expect(makeColorFromCSS('rgb(102, 51, 153)')).toEqual(PURPLE); }); it('works with rgba colors', () => { expect(makeColorFromCSS('rgba(0, 0, 0, 1)')).toEqual(BLACK); expect(makeColorFromCSS('rgba(255, 255, 255, 1)')).toEqual(WHITE); expect(makeColorFromCSS('rgba(102, 51, 153, 1)')).toEqual(PURPLE); }); it('multiplies rgba components with an alpha', () => { expect(makeColorFromCSS('rgba(0, 0, 0, 0.5)').alpha).toBeCloseTo(0.5); expect(makeColorFromCSS('rgba(0, 0, 0, 1)', 0.5).alpha).toBeCloseTo(0.5); expect(makeColorFromCSS('rgba(0, 0, 0, 0.5)', 0.5).alpha).toBeCloseTo(0.25); }); it('works with hsl colors', () => { expect(makeColorFromCSS('hsl(0, 0%, 0%)')).toEqual(BLACK); expect(makeColorFromCSS('hsl(0, 0%, 100%)')).toEqual(WHITE); }); it('works with hsla colors', () => { expect(makeColorFromCSS('hsla(0, 0%, 0%, 1)')).toEqual(BLACK); expect(makeColorFromCSS('hsla(0, 0%, 100%, 1)')).toEqual(WHITE); }); }); describe('makeColorFill', () => { it('sets the correct color', () => { expect(makeColorFill('#000')).toHaveProperty('color', BLACK); expect(makeColorFill('#fff')).toHaveProperty('color', WHITE); expect(makeColorFill('rebeccapurple')).toHaveProperty('color', PURPLE); expect(makeColorFill('#DFBA69')).toHaveProperty('color', GOLD); }); }); describe('makeRect', () => { it('is correctly constructed', () => { const group = makeRect(100, 200, 300, 400); expect(group).toHaveProperty('x', 100); expect(group).toHaveProperty('y', 200); expect(group).toHaveProperty('width', 300); expect(group).toHaveProperty('height', 400); }); }); describe('makeSymbolInstance', () => { it('is correctly constructed', () => { const instance = makeSymbolInstance( makeRect(0, 0, 100, 100), 'this is the symbol id', 'this is the name', ); expect(instance).toHaveProperty('symbolID', 'this is the symbol id'); expect(instance).toHaveProperty('name', 'this is the name'); }); }); describe('makeSymbolMaster', () => { it('is correctly constructed', () => { const master = makeSymbolMaster( makeRect(0, 0, 100, 100), 'this is the symbol id', 'this is the name', ); expect(master).toHaveProperty('symbolID', 'this is the symbol id'); expect(master).toHaveProperty('name', 'this is the name'); }); }); ================================================ FILE: __tests__/jest/jsonUtils/shapeLayers.ts ================================================ import { makeRectPath, makeShapePath, makeRectShapeLayer, makeShapeGroup, } from '../../../src/jsonUtils/shapeLayers'; describe('makeRectPath', () => { it('is correctly constructed', () => { const path = makeRectPath([10, 20, 30, 40]); expect(path.points[0]).toHaveProperty('cornerRadius', 10); expect(path.points[1]).toHaveProperty('cornerRadius', 20); expect(path.points[2]).toHaveProperty('cornerRadius', 30); expect(path.points[3]).toHaveProperty('cornerRadius', 40); }); }); describe('makeShapePath', () => { it('is correctly constructed', () => { const frame = { foo: 'bar' }; const path = { baz: 'qux' }; // @ts-ignore const shapePath = makeShapePath(frame, path); expect(shapePath).toHaveProperty('frame', frame); expect(shapePath).toHaveProperty('baz', 'qux'); }); }); describe('makeRectShapeLayer', () => { it('is correctly constructed', () => { const shapeLayer = makeRectShapeLayer(100, 200, 300, 400, [10, 20, 30, 40]); expect(shapeLayer).toHaveProperty('frame.x', 100); expect(shapeLayer).toHaveProperty('frame.y', 200); expect(shapeLayer).toHaveProperty('frame.width', 300); expect(shapeLayer).toHaveProperty('frame.height', 400); expect(shapeLayer).toHaveProperty('fixedRadius', 10); }); }); describe('makeShapeGroup', () => { it('is correctly constructed', () => { const frame = { foo: 'bar' }; const layers = [{ baz: 'qux' }]; const fills = ['foo', 'bar']; // @ts-ignore const shapeGroup = makeShapeGroup(frame, layers, undefined, undefined, fills); expect(shapeGroup).toHaveProperty('frame', frame); expect(shapeGroup).toHaveProperty('layers', layers); expect(shapeGroup).toHaveProperty('style.fills', fills); }); }); ================================================ FILE: __tests__/jest/jsonUtils/style.ts ================================================ import { makeBorderOptions, makeShadow } from '../../../src/jsonUtils/style'; describe('makeBorderOptions', () => { it('makes solid borders', () => { expect(makeBorderOptions('solid', 1)).toHaveProperty('dashPattern', []); }); it('makes dotted borders', () => { expect(makeBorderOptions('dotted', 1)).toHaveProperty('dashPattern', [1, 1]); expect(makeBorderOptions('dotted', 5)).toHaveProperty('dashPattern', [5, 5]); }); it('makes dashed borders', () => { expect(makeBorderOptions('dashed', 1)).toHaveProperty('dashPattern', [3, 3]); expect(makeBorderOptions('dashed', 5)).toHaveProperty('dashPattern', [15, 15]); }); }); describe('makeShadow', () => { it('has sensible defaults', () => { const result = makeShadow({}); expect(result).toHaveProperty('color.alpha', 1); expect(result).toHaveProperty('blurRadius', 1); expect(result).toHaveProperty('offsetX', 0); expect(result).toHaveProperty('offsetY', 0); }); it('passes through props', () => { const result = makeShadow({ shadowOpacity: 0.5, shadowColor: 'red', shadowRadius: 10, shadowOffset: { width: 5, height: 7, }, }); expect(result).toHaveProperty('color.alpha', 0.5); expect(result).toHaveProperty('blurRadius', 10); expect(result).toHaveProperty('offsetX', 5); expect(result).toHaveProperty('offsetY', 7); }); it('combines rgba alpha & shadowOpacity', () => { const result = makeShadow({ shadowOpacity: 0.5, shadowColor: 'rgba(0,0,0,0.5)', shadowRadius: 10, shadowOffset: { width: 5, height: 7, }, }); expect(result.color.alpha).toBeCloseTo(0.25); }); }); ================================================ FILE: __tests__/jest/reactTreeToFlexTree.ts ================================================ import yoga from 'yoga-layout-prebuilt'; import { computeYogaTree } from '../../src/jsonUtils/computeYogaTree'; import { Context } from '../../src/utils/Context'; import { reactTreeToFlexTree } from '../../src/buildTree'; import bridge from '../../src/platformBridges/macos'; const treeRootStub = { type: 'artboard', props: { style: { flexDirection: 'row', flexWrap: 'wrap', width: 416, }, name: 'Swatches', }, children: [ { type: 'view', props: { name: 'Layer 1', style: { height: 100, position: 'absolute', width: 100, zIndex: 1, }, }, children: [], }, { type: 'view', props: { name: 'Layer 3', style: { height: 300, position: 'absolute', width: 300, zIndex: 3, }, }, children: [], }, { type: 'view', props: { name: 'Layer 2', style: { height: 200, position: 'absolute', width: 200, zIndex: 2, }, }, children: [], }, ], }; describe('Compute Flex Tree', () => { it('correctly creates flex tree', () => { const yogaNode = computeYogaTree(bridge)(treeRootStub, new Context()); yogaNode.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR); const tree = reactTreeToFlexTree(treeRootStub, yogaNode, new Context()); expect(tree.children).toEqual([ { type: 'view', style: { height: 100, position: 'absolute', width: 100, zIndex: 1, }, textStyle: {}, layout: { left: 0, right: 0, top: 0, bottom: 0, width: 100, height: 100, }, props: { name: 'Layer 1', style: { height: 100, position: 'absolute', width: 100, zIndex: 1, }, textNodes: [], }, children: [], }, { type: 'view', style: { height: 200, position: 'absolute', width: 200, zIndex: 2, }, textStyle: {}, layout: { left: 0, right: 0, top: 0, bottom: 0, width: 200, height: 200, }, props: { name: 'Layer 2', style: { height: 200, position: 'absolute', width: 200, zIndex: 2, }, textNodes: [], }, children: [], }, { type: 'view', style: { height: 300, position: 'absolute', width: 300, zIndex: 3, }, textStyle: {}, layout: { left: 0, right: 0, top: 0, bottom: 0, width: 300, height: 300, }, props: { name: 'Layer 3', style: { height: 300, position: 'absolute', width: 300, zIndex: 3, }, textNodes: [], }, children: [], }, ]); }); }); ================================================ FILE: __tests__/jest/sharedStyles/TextStyles.ts ================================================ import bridge from '../../../src/platformBridges/macos'; let TextStyles; let doc; let sharedTextStyles; beforeEach(() => { jest.resetModules(); jest.mock('../../../src/utils/getSketchVersion', () => ({ getSketchVersion: jest.fn(() => 51), })); TextStyles = require('../../../src/sharedStyles/TextStyles').TextStyles; sharedTextStyles = require('../../../src/utils/sharedTextStyles'); jest.mock('../../../src/utils/sharedTextStyles'); TextStyles = TextStyles(() => bridge); sharedTextStyles = sharedTextStyles.sharedTextStyles; sharedTextStyles.setDocument = jest.fn((doc) => { if (!doc) { throw new Error('Please provide a sketch document reference'); } }); sharedTextStyles.addStyle = jest.fn(() => 'styleId'); sharedTextStyles.setStyles = jest.fn(() => sharedTextStyles); doc = jest.fn(); }); describe('create', () => { describe('without a context', () => { it('it errors', () => { const styles = {}; expect(() => TextStyles.create({}, styles)).toThrowError( /Please provide a sketch document reference/, ); }); }); describe('with a context', () => { it('clears clearExistingStyles when true', () => { TextStyles.create( {}, { clearExistingStyles: true, document: doc, }, ); expect(sharedTextStyles.setStyles).toHaveBeenCalled(); }); it('doesn’t clearExistingStyles when false', () => { TextStyles.create( {}, { clearExistingStyles: false, document: doc, }, ); expect(sharedTextStyles.setStyles).not.toHaveBeenCalled(); }); it('stores one style', () => { const styles = { foo: { fontSize: 'bar', }, }; const res = TextStyles.create(styles, { document: doc }); expect(Object.keys(res).length).toBe(1); }); it('stores unique styles seperately', () => { const styles = { foo: { fontSize: 'bar', }, bar: { fontSize: 'baz', }, }; const res = TextStyles.create(styles, { document: doc }); expect(Object.keys(res).length).toBe(2); expect(sharedTextStyles.addStyle).toHaveBeenCalledTimes(2); }); it('merges duplicate styles', () => { const styles = { foo: { fontSize: 'foo', }, bar: { fontSize: 'foo', }, }; const res = TextStyles.create(styles, { document: doc }); expect(Object.keys(res).length).toBe(1); expect(sharedTextStyles.addStyle).toHaveBeenCalledTimes(2); }); it('only stores text attributes', () => { const whitelist = [ 'color', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'textShadowOffset', 'textShadowRadius', 'textShadowColor', 'textTransform', 'letterSpacing', 'lineHeight', 'textAlign', 'writingDirection', ]; const blacklist = ['foo', 'bar', 'baz']; const input = [...whitelist, ...blacklist].reduce( (acc, key) => ({ ...acc, [key]: '', }), {}, ); const res = TextStyles.create({ foo: input }, { document: doc }); const firstStoredStyle = res[Object.keys(res)[0]].cssStyle; whitelist.forEach((key) => { expect(firstStoredStyle).toHaveProperty(key, ''); }); blacklist.forEach((key) => { expect(firstStoredStyle).not.toHaveProperty(key); }); }); }); }); describe('resolve', () => { beforeEach(() => { TextStyles.create({}, { document: doc }); }); it('retrieves a matching style', () => { const key = 'foo'; const styles = { [key]: { fontSize: 'bar' }, }; TextStyles.create(styles, { document: doc }); expect(TextStyles.resolve(styles[key])).toBeDefined(); expect(sharedTextStyles.addStyle).toHaveBeenCalledTimes(1); }); it('returns null with no matching style', () => { const key = 'foo'; const styles = { [key]: { fontSize: 'bar', }, }; const style2 = { fontSize: 'qux', }; TextStyles.create(styles, { document: doc }); expect(TextStyles.resolve(style2)).not.toBeDefined(); expect(sharedTextStyles.addStyle).toHaveBeenCalledTimes(1); }); }); describe('get', () => { it('finds a matching registered style by name', () => { const styles = { foo: { fontSize: 'bar', }, bar: { fontSize: 'baz', }, }; TextStyles.create(styles, { document: doc }); expect(TextStyles.get('foo')).toEqual(styles.foo); expect(TextStyles.get('baz')).toEqual(undefined); }); it('returns undefined when not found', () => { const styles = { foo: { fontSize: 'bar', }, }; TextStyles.create(styles, { document: doc }); expect(TextStyles.get('baz')).toEqual(undefined); }); }); describe('clear', () => { it('clears previously registered styles', () => { const styles = { foo: { fontSize: 'bar', }, bar: { fontSize: 'baz', }, }; TextStyles.create(styles, { document: doc }); TextStyles.clear(); expect(TextStyles.styles()).toEqual({}); }); }); ================================================ FILE: __tests__/jest/utils/isDefined.ts ================================================ import { isDefined } from '../../../src/utils/isDefined'; describe('isNullOrUndefined', () => { it('correctly identify null', () => { const shouldBeNull = isDefined(null); expect(shouldBeNull).toEqual(false); }); it('correctly identify undefined', () => { const shouldBeUndefined = isDefined(undefined); expect(shouldBeUndefined).toEqual(false); }); it('correctly identify zero (0)', () => { const shouldBeZero = isDefined(0); expect(shouldBeZero).toEqual(true); }); }); ================================================ FILE: __tests__/jest/utils/sortObjectKeys.ts ================================================ import { sortObjectKeys } from '../../../src/utils/sortObjectKeys'; test('simple example', () => { const a = { foo: true, bar: true, qux: true, baz: true, }; const b = { bar: true, baz: true, foo: true, qux: true, }; expect(sortObjectKeys(a)).toEqual(b); }); ================================================ FILE: __tests__/jest/utils/zIndex.ts ================================================ import { zIndex } from '../../../src/utils/zIndex'; const noZIndexNode = { props: { style: { zIndex: 0, }, }, }; const zIndexNode = { props: { style: { zIndex: 1, }, }, }; const fixureThatShouldBeSortedDifferently = [noZIndexNode, zIndexNode]; const fixureThatShouldNotBeSortedDifferently = [noZIndexNode, noZIndexNode]; describe('zIndex', () => { it('correctly resort zIndex', () => { // @ts-ignore const shouldBeResorted = zIndex(fixureThatShouldBeSortedDifferently); // @ts-ignore expect(shouldBeResorted[0].props.style.zIndex).toEqual(0); }); it('correctly add original index to returned objects ', () => { // @ts-ignore const shouldBeResorted = zIndex(fixureThatShouldBeSortedDifferently); // @ts-ignore expect(shouldBeResorted[0].oIndex).toEqual(0); }); it('correctly resort zIndexes that are all the same', () => { // @ts-ignore const shouldNotBeResorted = zIndex(fixureThatShouldNotBeSortedDifferently); // @ts-ignore expect(shouldNotBeResorted[0].props.style.zIndex).toEqual(0); }); }); ================================================ FILE: __tests__/skpm/basic.test.js ================================================ import * as React from 'react'; import * as sketch from 'sketch'; import { render, View, Artboard, Text } from '../../lib'; // depending on where those tests run, we don't get the things, // eg. the context might be empty or there is no selected document // This make sure we always get something function getDoc(context, document) { return context.document || (sketch.getSelectedDocument() || document).sketchObject; } const colorList = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur Dark': '#24828F', Peach: '#EFADA0', 'Peach Dark': '#E37059', Pear: '#93DAAB', 'Pear Dark': '#2E854B', }; test('should render a Page with a rectangle', (context, document) => { const nativePage = getDoc(context, document).currentPage(); const Swatch = ({ name, hex }) => ( {name} {hex} ); render( {Object.keys(colorList).map(color => ( ))} , nativePage, ); const page = sketch.Page.fromNative(nativePage); expect(page.layers[0].name).toBe('Swatches'); }); ================================================ FILE: __tests__/skpm/render-context.test.js ================================================ import * as React from 'react'; import * as sketch from 'sketch'; import { render, View, Text } from '../../lib'; // depending on where those tests run, we don't get the things, // eg. the context might be empty or there is no selected document // This make sure we always get something function getDoc(document) { return sketch.getSelectedDocument() || document; } test('should render a Page with context events', (context, document) => { const { selectedPage } = getDoc(document); const Swatch = ({ hex }) => { const [count, setCount] = React.useState(0); React.useEffect(() => { setCount(10); }, [count]); return ( Count is {count} ); }; render(, selectedPage); expect(selectedPage.layers[0].name).toBe('Count is 10'); }); ================================================ FILE: __tests__/skpm/render-in-wrapped-object.test.js ================================================ import * as React from 'react'; import * as sketch from 'sketch'; import { render, View, Artboard, Text } from '../../lib'; // depending on where those tests run, we don't get the things, // eg. the context might be empty or there is no selected document // This make sure we always get something function getDoc(document) { return sketch.getSelectedDocument() || document; } const colorList = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur Dark': '#24828F', Peach: '#EFADA0', 'Peach Dark': '#E37059', Pear: '#93DAAB', 'Pear Dark': '#2E854B', }; test('should render a Page with a rectangle', (context, document) => { const { selectedPage } = getDoc(document); const Swatch = ({ name, hex }) => ( {name} {hex} ); render( {Object.keys(colorList).map(color => ( ))} , selectedPage, ); expect(selectedPage.layers[0].name).toBe('Swatches'); }); ================================================ FILE: book.json ================================================ { "gitbook": ">= 3.2.1", "title": "react-sketchapp", "plugins": [ "edit-link", "prism", "-highlight", "github", "-search", "codeblock-disable-glossary", "anchorjs" ], "pluginsConfig": { "edit-link": { "base": "https://github.com/airbnb/react-sketchapp/tree/master", "label": "Edit This Page" }, "github": { "url": "https://github.com/airbnb/react-sketchapp/" } } } ================================================ FILE: docs/API.md ================================================ # API Reference - [`render`](#renderelement-container) - [`renderToJSON`](#rendertojsonelement) - [Components](#components) - [``](#document) - [``](#page) - [``](#artboard) - [``](#image) - [``](#redbox) - [``](#svg) - [``](#text) - [``](#view) - [`Hooks`](#hooks) - [`useWindowDimensions`](#usewindowdimensions) - [`Platform`](#platform) - [`OS`](#os) - [`Version`](#version) - [`select`](#selectobj) - [`StyleSheet`](#stylesheet) - [`hairlineWidth`](#hairlinewidth) - [`absoluteFill`](#absolutefill) - [`create`](#createstyles) - [`flatten`](#flattenstyles) - [`resolve`](#resolvestyle) - [`TextStyles`](#textstyles) - [`create`](#createstyleoptionsstyles) - [`resolve`](#resolvestyle) - [`Symbols`](#symbols) - [`makeSymbol`](#makesymbolnode-props-document) ### `render(element, container)` Returns the top-level rendered Sketch object or an array of Sketch objects if you use `` components. #### Parameters ##### `element` (required) Top-level React component that defines your Sketch document. Example: ```js Hello World ``` ##### `container` (optional) The element to render into - will be replaced. Should either be a Sketch [Document](https://developer.sketchapp.com/reference/api/#document), Sketch [Group](https://developer.sketchapp.com/reference/api/#group) or Sketch [Page](https://developer.sketchapp.com/reference/api/#page) Object. Example: `sketch.getSelectedDocument().selectedPage`. #### Returns The top-most rendered native Sketch layer. #### Example ```js import sketch from 'sketch'; import { View, Text, render } from 'react-sketchapp'; const Document = props => ( Hello world! ); export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` ### `renderToJSON(element)` Returns a Sketch JSON object for further consumption - doesn't add to the page. #### Parameters ##### `element` (required) Top-level React component that defines your Sketch document. #### Returns The top-most Sketch layer as JSON. ## Components ### `` Wrapper for Sketch's Documents. Must be used at the root of your application and is required if you would like to have multiple pages. #### Props | Prop | Type | Default | Note | | ---------- | ------ | ------- | ---------------------------------------- | | `children` | `Node` | | Can only be [``](#page) components | #### Example ```js Hello world! Hello second world!! ``` ### `` Wrapper for Sketch's Pages. Requires a [``](#document) component as a parent if you would like to use multiple of these components. #### Props | Prop | Type | Default | Note | | ---------- | -------- | ------- | ------------------------------------------------ | | `name` | `String` | | The name to be displayed in the Sketch Page List | | `children` | `Node` | | | #### Example ```js Hello world! ``` ### `` Wrapper for Sketch's Artboards. Requires a [``](#page) component as a parent if you would like to use multiple of these components. #### Props | Prop | Type | Default | Note | | --- | --- | --- | --- | | `name` | `String` | | The name to be displayed in the Sketch Layer List | | `children` | `Node` | | | | `style` | [`Style`](/docs/styling.md) | | | | `viewport` | `Viewport` | | Object: { name: string, width: number, height: number, scale?: number, fontScale?: number } | | `isHome` | `Boolean` | | Is prototype home screen if true | The `scale` and `fontScale` attributes in the `viewport` prop are not used by Sketch, but can be used together with the [`useWindowDimensions`](#usewindowdimensions) hook for conditional styling/rendering. #### Examples Hello world with width of 480px. ```js Hello world! ``` Mobile screen artboard with viewport preset (supports scrolling in prototypes). ```js Hello world! ``` ### `` #### Props | Prop | Type | Default | Note | | ------------ | --------------------------- | --------- | ---- | | `children` | `Node` | | | | `source` | `ImageSource` | | | | `style` | [`Style`](/docs/styling.md) | | | | `resizeMode` | `ResizeMode` | `contain` | | ```js type ImageSource = string | { src: string }; type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat' | 'none'; ``` #### Example ```js ``` ### `` A red box / 'red screen of death' error handler. Thanks to [commissure/redbox-react](https://github.com/commissure/redbox-react). #### Props | Prop | Type | Default | Note | | --- | --- | --- | --- | | `error` | [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) | **required** | A JavaScript Error object | #### Example ```js import sketch from 'sketch'; import { RedBox, render } from 'react-sketchapp'; export default () => { const { selectedPage } = sketch.getSelectedDocument(); try { render(, selectedPage); } catch (err) { render(, selectedPage); } }; ``` ### `` SVG Interface to Sketch The API is based on [`react-native-svg`](https://github.com/react-native-community/react-native-svg). See more information on [its README](https://github.com/react-native-community/react-native-svg#Usage). #### Example ```js import sketch from 'sketch'; import { Svg, render } from 'react-sketchapp'; export default () => { render( , sketch.getSelectedDocument().selectedPage, ); }; ``` #### Direct imports Additionally, to have a somewhat more compliant mode to the `react-native-svg` API, the SVG components might as well be imported directly: ```js import sketch from 'sketch'; import { render } from 'react-sketchapp'; import Svg, { G, Path } from 'react-sketchapp/lib/components/Svg'; export default () => { render( , sketch.getSelectedDocument().selectedPage, ); }; ``` ### `` Text primitives #### Props | Prop | Type | Default | Note | | --- | --- | --- | --- | | `name` | `String` | | The name to be displayed in the Sketch Layer List | | `children` | `String` | | | | `style` | [`Style`](/docs/styling.md) | | | #### Example ```js Hello World! ``` ### `` View primitives #### Props | Prop | Type | Default | Note | | --- | --- | --- | --- | | `name` | `String` | | The name to be displayed in the Sketch Layer List | | `children` | `Node` | | | | `style` | [`Style`](/docs/styling.md) | | | | `flow` | `Flow` | | Object: { target: string, targetId: string, animationType: string } | #### Examples ##### Example with children ```js Hello World! Hello World! Hello World! ``` ##### Example using `flow` prop for prototyping destination ```js Open menu! id */> Go back! ``` ## Hooks ### `useWindowDimensions()` Returns the window dimensions of the parent ``. Returns `{ width: number, height: number, fontScale: number, scale: number }`. #### Example ```js import { Page, Artboard, View, Text, useWindowDimensions } from 'react-sketchapp'; const HomePage = () => { const { height, width } = useWindowDimensions(); return ( Hello World {(width >= 768) && ( You can only see this text on tablet/desktop )} ); }; const devices = [{ name: 'Mobile', width: 360, height: 640, }, { name: 'Tablet', width: 768 height: 1024, }, { name: 'Desktop', width: 1024 height: 1280, }]; render( {devices.map(viewport => ( ))} , ); ``` ## Platform ### `OS` `sketch` ### `Version` `1` ### `select(obj)` #### Parameters ##### `obj` ## StyleSheet Compared to single-use `style` objects, `StyleSheets` enable creation of re-usable, optimized style references. ### `hairlineWidth` The platform's global 'hairline width'. ### `absoluteFill` A constant 'absolute fill' style. ### `create(styles)` Create an optimized `StyleSheet` reference from a style object. #### Parameters ##### `styles` #### Example ```js const styles = StyleSheet.create({ foo: { fontSize: 24, color: 'red', }, bar: { fontSize: 36, color: 'blue', }, }); // { foo: 1, bar: 2 } ; ``` ### `flatten(styles)` Flatten an array of style objects into one aggregated object, **or** look up the definition for a registered stylesheet. #### Parameters ##### `styles` #### Example ```js const styles = StyleSheet.create({ foo: { fontSize: 24, color: 'red', }, bar: { backgroundColor: 'blue', lineHeight: 36, }, }); StyleSheet.flatten([styles.foo, styles.bar]); // { fontSize: 24, color: 'red', backgroundColor: 'blue', lineHeight: 36 } // alternatively: StyleSheet.flatten(styles.foo); // { fontSize: 24, color: 'red' } ``` ### `resolve(style)` Resolve one style. #### Parameters ##### `style` #### Example ```js const styles = StyleSheet.create({ foo: { fontSize: 24, color: 'red', }, }); StyleSheet.resolve(styles.foo); // { fontSize: 24, color: 'red' } ``` ## TextStyles An interface to Sketch's shared text styles. Create styles with or without rendering them to the document canvas. ### `create(styles, options)` The primary interface to TextStyles. **Call this before rendering**. #### Parameters ##### `styles` **(required)** An object of JavaScript styles. The keys will be used as Sketch's Text Style names. ##### `options: { document, clearExistingStyles }` ###### `document` The Sketch Document currently being rendered into. ###### `clearExistingStyles` Clear any styles already registered in the document. #### Example ```js import sketch from 'sketch'; import { TextStyles, View, Text, render } from 'react-sketchapp'; export default () => { const typeStyles = { Headline: { fontSize: 36, fontFamily: 'Apercu', lineHeight: 38, }, Body: { fontSize: 16, fontFamily: 'Helvetica', lineHeight: 22, }, }; TextStyles.create( { context: context, clearExistingStyles: true, }, typeStyles, ); const Document = () => ( Headline text Body text ); render(, sketch.getSelectedDocument().selectedPage); }; ``` ### `resolve(style)` Find a stored native Sketch style object for a given JavaScript style object. You probably don't need to use this. #### Parameters ##### `style` A JavaScript style ### `styles` Find all of the registered styles. You probably don't need to use this. ### `get(name)` Find a text style by _name_. #### Parameters ##### `name` The style name #### Example ```js import sketch from 'sketch'; import { TextStyles, View, Text, render } from 'react-sketchapp'; export default () => { const typeStyles = { Headline: { fontSize: 36, fontFamily: 'Apercu', lineHeight: 38, }, Body: { fontSize: 16, fontFamily: 'Helvetica', lineHeight: 22, }, }; TextStyles.create( { context: context, clearExistingStyles: true, }, typeStyles, ); const Document = () => ( Headline text Body text ); render(, sketch.getSelectedDocument().selectedPage); }; ``` ### `clear` Reset the registered styles. ## Symbols An interface to Sketch's symbols. Create symbols and optionally inject them into the symbols page. ### `makeSymbol(node, props, document)` Creates a new symbol and injects it into the `Symbols` page. The name of the symbol can be optionally provided and will default to the display name of the component. Returns a react component which is an can be used to render instances of the symbol. #### Parameters | Parameter | Type | Default | Note | | --- | --- | --- | --- | | `node` | `Node` | | The node object that will be rendered as a symbol | | `props` | `Object` | The node name | Optional name for the symbol, string can include backslashes to organize these symbols with Sketch. For example `squares/blue` | | `props.name` | `String` | The node name | Optional name for the symbol, string can include backslashes to organize these symbols with Sketch. For example `squares/blue` | | `props.style` | [`Style`](/docs/styling.md) | | | | `document` | `Object` | The current document | The Sketch document to make the symbol in | ### `getSymbolComponentByName(name)` Returns a react component which can be used to render the symbol instance that is associated with that name. ### `getSymbolMasterByName(name)` Returns the JSON representation of the symbol master that is associated with that name. ### Symbol example ```js import sketch from 'sketch'; import { View, makeSymbol, Artboard, render } from 'react-sketchapp'; const BlueSquare = () => ( ); const BlueSquareSymbol = makeSymbol(BlueSquare); const Document = () => ( ); export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` ### Text override example Text overrides use the name parameter to target a specific Text primitive. When no name is given the value within the Text primitive can be used to override the value. ```js import sketch from 'sketch'; import { View, Text, makeSymbol, Artboard, render } from 'react-sketchapp'; const BlueSquare = () => ( Blue Square Text ); const BlueSquareSymbol = makeSymbol(BlueSquare, 'squares/blue'); const Document = () => ( ); export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` ### Image override example Image overrides use the name parameter to target a specific Image primitive. ```js import sketch from 'sketch'; import { View, Image, Artboard, makeSymbol, render } from 'react-sketchapp'; const BlueSquare = () => ( ); const BlueSquareSymbol = makeSymbol(BlueSquare, 'squares/blue'); const Document = () => ( ); export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` #### Nested symbol + override example ```js import sketch from 'sketch'; import { View, Text, makeSymbol, Image, Artboard, render } from 'react-sketchapp'; const RedSquare = () => ( Red Square ); const RedSquareSymbol = makeSymbol(RedSquare, 'squares/red'); const BlueSquare = () => ( Blue Square ); const BlueSquareSymbol = makeSymbol(BlueSquare, 'squares/blue'); const Photo = () => ( ); const PhotoSymbol = makeSymbol(Photo); const Nested = () => ( ); const NestedSymbol = makeSymbol(Nested); const Document = () => ( ); export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` ================================================ FILE: docs/FAQ.md ================================================ # Frequently Asked Questions #### Why?!??! `react-sketchapp` evolved out of our need to generate **high-quality, consistent Sketch assets** for our design system at Airbnb. Wrapping Sketch’s imperative API is a pragmatic solution for a great developer experience and predictable rendering. #### How do I `console.log`? You have multiple options to view the logs: - Using the [sketch-dev-tools](https://github.com/skpm/sketch-dev-tools) - `Console.app -> ~/Library/Logs -> com.bohemiancoding.sketch -> Plugin Output.log` - in the terminal ```bash skpm log -f ``` - in the terminal ```bash tail -F ~/Library/Logs/com.bohemiancoding.sketch3/Plugin\ Output.log ``` Occasionally this file disappears — in that case, run this and then try `tail`ing again. ```bash touch ~/Library/Logs/com.bohemiancoding.sketch3/Plugin\ Output.log ``` For more information, check out the [Sketch developer documentation](https://developer.sketch.com/plugins/debugging). #### I'm running a project as a plugin & Sketch isn't showing my changes Sketch has a [developer mode](https://developer.sketch.com/plugins/debugging#reload-scripts) which refreshes plugins before running. If you're using `skpm` this should be set up automatically, but just in case try running ```bash defaults write com.bohemiancoding.sketch3.plist AlwaysReloadScript -bool YES ``` #### `` & ``? Where are the shapes? Talk to me about your API decisions! Early versions of `react-sketchapp` mirrored Sketch's layers — ``, ``, `` etc. This was adequate for rendering simplistic designs such as grids of color palettes, but our focus is on production design systems. At some point, we had to translate from our component codebase's primitives to Sketch's shapes. We tried translating trees of React Native elements into ``s etc, but it felt clumsy. Not every Sketch property has an analog in react-native, but **most react-native properties are translatable to Sketch**. By aligning with react-native's API we: - think in the same primitives as we actually use in production - use the same layout algorithm in design & code - [render real components](http://airbnb.io/react-sketchapp/docs/guides/universal-rendering.html) into Sketch with `react-primitives` (a platform independent set of primitives) Where it makes sense we're open to creating Sketch-specific components —there's no analog for `` on web or mobile—but the goal of `react-sketchapp` is to bring design & engineering closer together. #### So I can't draw arbitrary shapes? You can use the [SVG API](/docs/API.md#svg) to draw arbitrary shapes. #### Any plans to support Sketch's constraints for layout? Not currently. FlexBox is the closest we have to a predictable, cross-platform layout specification — by using it, we can use the same styles on every platform we build for. We currently use [`yoga`](https://github.com/facebook/yoga). #### Is there two-way binding? Can I generate React components from Sketch? 🔁 Nope. Isomorphisms are compelling but our focus is on tools that we can use day-to-day to improve the productivity of designers and engineers working on large-scale production applications. Getting production-ready semantics out of Sketch is more difficult than generating production-ready Sketch templates from React components 💀 Our solution is to keep our [our design system](http://airbnb.design/building-a-visual-language/)’s source of truth in code, and use `react-sketchapp` to compose & consume it. To _edit_ our design system, we are free to leverage any technology that can create React components, or be compiled to JSX, such as: - [React-centric IDEs](https://www.decosoftware.com/) - in-house design tools that are tailored to our workflow (whilst being backed by data, version control & semantic versioning) 🔜 👀 - writing React components in text editors with our fingers #### Does this tie your workflow to Sketch? What about other design tools? Treating Sketch primarily as a _rendering target_ for cross-platform components pushes you to store components & style in code — you're then free to build translation layers for any other design tool that exposes an API. Given equivalent API support it would be possible to simultaneously render to `react-sketchapp`, `react-figma`, `react-xd` & `react-quark`. Rather than tying us into one design tools, reasoning about design in cross-platform primitives _frees us_ to use the tooling we want. #### Can I use [TypeScript](https://www.typescriptlang.org/)? Of course! TypeScript definitions are published with the npm package. ================================================ FILE: docs/README.md ================================================ ## Table of Contents - [Introduction](/README.md) - [Guides](/docs/guides/README.md) - [Getting Started](/docs/guides/getting-started.md) - [Using `skpm` as a build system](/docs/guides/using-skpm.md) - [Rendering](/docs/guides/rendering.md) - [Data Fetching](/docs/guides/data-fetching.md) - [Universal Rendering](/docs/guides/universal-rendering.md) - [Styling](/docs/guides/styling.md) - [API Reference](/docs/API.md) - [render](/docs/API.md#renderelement-container) - [renderToJSON](/docs/API.md#rendertojsonelement) - [Document](/docs/API.md#document) - [Page](/docs/API.md#page) - [Artboard](/docs/API.md#artboard) - [Image](/docs/API.md#image) - [RedBox](/docs/API.md#redbox) - [Svg](/docs/API.md#svg) - [Text](/docs/API.md#text) - [View](/docs/API.md#view) - [Platform](/docs/API.md#platform) - [StyleSheet](/docs/API.md#stylesheet) - [TextStyles](/docs/API.md#textstyles) - [Symbols](/docs/API.md#symbols) - [Examples](/docs/examples.md) - [Change Log](/CHANGELOG.md) - [FAQ](/docs/FAQ.md) - [Contributing](https://github.com/airbnb/react-sketchapp/blob/master/.github/CONTRIBUTING.md) ================================================ FILE: docs/examples.md ================================================ # Examples `react-sketchapp` is bundled with lots of examples! ### [Basic setup](https://github.com/airbnb/react-sketchapp/tree/master/examples/basic-setup) ![Basic setup](https://cloud.githubusercontent.com/assets/591643/24778192/1f0684ec-1ade-11e7-866b-b11bb60ac109.png) ### [Style guide](https://github.com/airbnb/react-sketchapp/tree/master/examples/styleguide) ![Style guide](https://cloud.githubusercontent.com/assets/591643/24778196/2a4ef41a-1ade-11e7-9805-8d974bbfd708.png) ### [Profile Cards](https://github.com/airbnb/react-sketchapp/tree/master/examples/profile-cards) ![examples-profile-cards](https://cloud.githubusercontent.com/assets/591643/24778173/0dd7c03c-1ade-11e7-8bad-1ad51fe1033e.png) ### [Profile Cards on Web + Sketch w/ `react-primitives`](https://github.com/airbnb/react-sketchapp/tree/master/examples/profile-cards-primitives) ![examples-primitives](https://cloud.githubusercontent.com/assets/591643/24778174/0dd9d214-1ade-11e7-9372-fa8b578a0b48.png) ### [Profile Cards w/ `react-with-styles`](https://github.com/airbnb/react-sketchapp/tree/master/examples/profile-cards-react-with-styles) ![examples-primitives](https://cloud.githubusercontent.com/assets/591643/24778174/0dd9d214-1ade-11e7-9372-fa8b578a0b48.png) ### [Profile Cards w/ GraphQL](https://github.com/airbnb/react-sketchapp/tree/master/examples/profile-cards-graphql) ![Profile Cards w/ GraphQL](https://cloud.githubusercontent.com/assets/591643/24778175/0dda21d8-1ade-11e7-9545-13b472263ff6.png) ### [Venue Search on Web + Sketch w/ `react-primitives`, Foursquare & Google Maps](https://github.com/airbnb/react-sketchapp/tree/master/examples/foursquare-maps) ![foursquare-maps](https://cloud.githubusercontent.com/assets/591643/25052095/f666928e-2104-11e7-805c-a3c73ffcabcb.png) ### [Generative Colors w/ Chroma-JS](https://github.com/airbnb/react-sketchapp/tree/master/examples/colors) ![examples-colors](https://cloud.githubusercontent.com/assets/591643/24778153/efc76cdc-1add-11e7-93dd-0351f8f428c0.png) ### [Timeline w/ AirTable](https://github.com/airbnb/react-sketchapp/tree/master/examples/timeline-airtable) ![timeline-AirTable](https://cloud.githubusercontent.com/assets/21080/25830456/cdc67bf8-3411-11e7-8998-fdef507ab0d2.png) ### [Basic setup w/ Typescript](https://github.com/airbnb/react-sketchapp/tree/master/examples/basic-setup-typescript) ![typings](./assets/tooling-typings-vs-code.png) ![examples-basic](https://cloud.githubusercontent.com/assets/591643/24778192/1f0684ec-1ade-11e7-866b-b11bb60ac109.png) ================================================ FILE: docs/guides/README.md ================================================ # Guides How to use `react-sketchapp` for fun and profit. - [Getting Started](getting-started.md) - [Using `skpm` as a build system](using-skpm.md) - [Rendering](rendering.md) - [Data Fetching](data-fetching.md) - [Universal Rendering](universal-rendering.md) - [Styling](styling.md) ================================================ FILE: docs/guides/data-fetching.md ================================================ # Data Fetching Pull real data from an API with `fetch` or GraphQL. ## Fetch [Full example](https://github.com/airbnb/react-sketchapp/tree/master/examples/foursquare-maps) `skpm` automatically provides the [Sketch `fetch` polyfill](https://github.com/skpm/sketch-polyfill-fetch) — just use `fetch` as usual. ```js import fetch from 'sketch-module-fetch-polyfill'; import { render } from 'react-sketchapp'; import MyApp from './MyApp'; export default context => { fetch('https://reqres.in/api/users') .then(res => res.json()) .then(data => { render(, context.document.currentPage()); }); }; ``` ## GraphQL [Full example](https://github.com/airbnb/react-sketchapp/tree/master/examples/profile-cards-graphql) [`gql-sketch`](https://github.com/jongold/gql-sketch) provides a convenient interface for interacting with GraphQL APIs. ```bash npm install gql-sketch --save ``` ```js import Client from 'gql-sketch'; import { render } from 'react-sketchapp'; import MyApp from './MyApp'; export default context => { Client('http://example.com/my-graphql-endpoint') .query( ` { allFilms { films { title, actor, catchphrase } } } `, ) .then(({ allFilms }) => { render(, context.document.currentPage()); }); }; ``` ================================================ FILE: docs/guides/getting-started.md ================================================ # Getting Started You can create a `react-sketchapp` project with `skpm`, by cloning a ready-made [example](../examples.md), or by manually setting up the `package.json` and `manifest.json` scripts (advanced usage). ## Environment Setup You will need npm, Node and Sketch. - Terminal (if you’re new to the command line, this [guide](https://medium.com/32pixels/the-designers-guide-to-the-osx-command-prompt-71b0016cac31) may help) - You need to make sure `git` is installed – type `git --version` in your Terminal to check if it's installed, if it isn’t, you should be prompted to install via “command line developer tools”. - Code editor e.g. [VSCode](https://code.visualstudio.com/), [Atom](https://atom.io/) - Node.js & `npm` – [install with Homebrew](https://nodejs.org/en/download/package-manager/#macos) (or install with [Node Version Manager](https://nodejs.org/en/download/package-manager/#nvm)) - [Sketch](https://www.sketch.com/) - requires macOS ## Creating a Project With Skpm **Replace** `my-app` with your desired project name: ### Installation ```bash npm install --global skpm skpm create my-app --template=airbnb/react-sketchapp # template is a GitHub repo cd my-app ``` ### Setup You can now open `my-app` in your code editor of choice. You will see a `src` folder with a `manifest.json` file and Sketch entrypoint (e.g. `my-command.js`). If you wish to rename `my-command.js`, you can do so and update the file name in `script` in `manifest.json` Example modifications (assuming we want to rename the entrypoint file to `main.js` and don't want to have sub-commands): `src/manifest.json` ```diff "commands": [ { - "name": "my-command", + "name": "My App Name: Sketch Components" - "identifier": "my-command-identifier", + "identifier": "main", - "script": "./my-command.js" + "script": "./main.js" } ], "menu": { - "title": "my-app", - "items": [ - "my-command-identifier" - ] + "isRoot": true, + "items": [ + "main" + ] + } } ``` ### Rendering to Sketch To render your app to Sketch, open the Sketch application, create a new blank document, then go to your Terminal and run: ```bash # Make sure you've already done `cd my-app` npm run render ``` You can pass the target Sketch container layer (i.e. document, group or page object) to the `render` function in your Sketch plugin entrypoint file, using the Sketch API: `render(, sketch.getSelectedDocument()`. For more info on rendering to Sketch, see the [rendering](./rendering.md) page. ================================================ FILE: docs/guides/rendering.md ================================================ # Rendering Guide You can use the Sketch API to select Sketch containers such as documents, pages or groups, to pass through to the `render` function. ### Rendering to Multiple Pages or New Documents `src/my-command.js` (or whatever file your Sketch plugin entrypoint is). ```js import React from 'react'; import { render, Document, Page } from 'react-sketchapp'; // wrapper is required if you want to use multiple pages const App = () => ( Hello World! Hello World, again! ); export default () => { const documents = sketch.getDocuments(); const document = sketch.getSelectedDocument() || new sketch.Document(); // get the current document // or create a new document }; ``` ## Rendering to Selected Document This will render to the last active document. If there is no document open, document will be undefined and you will get an error, so you can add `|| new sketch.Document()` as a fallback to handle this. ```js import sketch from 'sketch'; import { render } from 'react-sketchapp'; // const App = () => ... or import App from './App'; export default () => { const document = sketch.getSelectedDocument(); render(, document); }; ``` ## Rendering to Document by Name We can select a document by name, by looping through `sketch.getDocuments()` and checking `doc.path` inside the loop. ```js import path from 'path'; import sketch from 'sketch'; import { render } from 'react-sketchapp'; // const App = () => ... or import App from './App'; const getDocumentByName = name => { return (sketch.getDocuments() || []).find(doc => { return doc.path && path.basename(doc.path, '.sketch') === name; }); }; export default () => { const document = getDocumentByName('My App Design') || new sketch.Document(); // Fallback to new document if document not found render(, document); }; ``` ================================================ FILE: docs/guides/styling.md ================================================ # Styling Components use CSS styles + FlexBox layout. ## Layout Styles | property | type | supported? | | --- | --- | --- | | `width` | `number` | `percentage` | ✅ | | `height` | `number` | `percentage` | ✅ | | `top` | `number` | `percentage` | ✅ | | `left` | `number` | `percentage` | ✅ | | `right` | `number` | `percentage` | ✅ | | `bottom` | `number` | `percentage` | ✅ | | `minWidth` | `number` | `percentage` | ✅ | | `maxWidth` | `number` | `percentage` | ✅ | | `minHeight` | `number` | `percentage` | ✅ | | `maxHeight` | `number` | `percentage` | ✅ | | `margin` | `number` | `percentage` | ✅ | | `marginVertical` | `number` | `percentage` | ✅ | | `marginHorizontal` | `number` | `percentage` | ✅ | | `marginTop` | `number` | `percentage` | ✅ | | `marginBottom` | `number` | `percentage` | ✅ | | `marginLeft` | `number` | `percentage` | ✅ | | `marginRight` | `number` | `percentage` | ✅ | | `padding` | `number` | `percentage` | ✅ | | `paddingVertical` | `number` | `percentage` | ✅ | | `paddingHorizontal` | `number` | `percentage` | ✅ | | `paddingTop` | `number` | `percentage` | ✅ | | `paddingBottom` | `number` | `percentage` | ✅ | | `paddingLeft` | `number` | `percentage` | ✅ | | `paddingRight` | `number` | `percentage` | ✅ | | `borderWidth` | `number` | `percentage` | ✅ | | `borderTopWidth` | `number` | `percentage` | ✅ | | `borderRightWidth` | `number` | `percentage` | ✅ | | `borderBottomWidth` | `number` | `percentage` | ✅ | | `borderLeftWidth` | `number` | `percentage` | ✅ | | `position` | `absolute` | `relative` | ✅ | | `flexDirection` | `row` | `row-reverse` | `column` | `column-reverse` | ✅ | | `flexWrap` | `wrap` | `nowrap` | ✅ | | `justifyContent` | `flex-start` | `flex-end` | `center` | `space-between` | `space-around` | ✅ | | `alignItems` | `flex-start` | `flex-end` | `center` | `stretch` | ✅ | | `alignSelf` | `auto` | `flex-start` | `flex-end` | `center` | `stretch` | ✅ | | `overflow` | `visible` | `hidden` | `scroll` | ✅ | | `flex` | `number` | ✅ | | `flexGrow` | `number` | ✅ | | `flexShrink` | `number` | ✅ | | `flexBasis` | `number` | ✅ | | `aspectRatio` | `number` | ⛔️ | | `zIndex` | `number` | ✅ | | `backfaceVisibility` | `visible` | `hidden` | ⛔️ | | `backgroundColor` | `Color` | ✅ | | `borderColor` | `Color` | ✅ | | `borderTopColor` | `Color` | ✅ | | `borderRightColor` | `Color` | ✅ | | `borderBottomColor` | `Color` | ✅ | | `borderLeftColor` | `Color` | ✅ | | `borderRadius` | `number` | `percentage` | ✅ | | `borderTopLeftRadius` | `number` | `percentage` | ✅ | | `borderTopRightRadius` | `number` | `percentage` | ✅ | | `borderBottomLeftRadius` | `number` | `percentage` | ✅ | | `borderBottomRightRadius` | `number` | `percentage` | ✅ | | `borderStyle` | `solid` | `dotted` | `dashed` | ✅ | | `borderWidth` | `number` | `percentage` | ✅ | | `borderTopWidth` | `number` | `percentage` | ✅ | | `borderRightWidth` | `number` | `percentage` | ✅ | | `borderBottomWidth` | `number` | `percentage` | ✅ | | `borderLeftWidth` | `number` | `percentage` | ✅ | | `opacity` | `number` | ✅ | ## Shadow Styles | property | type | supported? | | --- | --- | --- | | `shadowColor` | `Color` | ✅ | | `shadowOffset` | `{ width: number, height: number }` | ✅ | | `shadowOpacity` | `number` | ✅ | | `shadowRadius` | `number` | `percentage` | ✅ | ## Type Styles | property | type | supported? | | --- | --- | --- | | `color` | `Color` | ✅ | | `fontFamily` | `string` | ✅ | | `fontSize` | `number` | ✅ | | `fontStyle` | `normal` | `italic` | ✅ | | `fontWeight` | `string` | `number` | ✅ | | `textDecorationLine` | `none` | `underline` | `double` | `line-through` | ✅ | | `textShadowOffset` | `{ width: number, height: number }` | ✅ | | `textShadowRadius` | `number` | ✅ | | `textShadowColor` | `Color` | ✅ | | `textTransform` | `none` | `uppercase` | `lowercase` | ✅ | | `letterSpacing` | `number` | ✅ | | `lineHeight` | `number` | ✅ | | `textAlign` | `auto` | `left` | `right` | `center` | `justify` | ✅ | | `writingDirection` | `auto` | `ltr` | `rtl` | ⛔️ | | `opacity` | `number` | ✅ | | `percentage` | `points` | `percentages` | ✅ | ## Styles Specific To `react-sketchapp` Some properties are Sketch specific and won't work cross-platform but give you a better control over your components. | property | type | supported? | | --- | --- | --- | | `shadowSpread` | `number` | ✅ | | `shadowInner` | `boolean` | ✅ | ## Examples Styles can be passed to components as plain objects, or via [`StyleSheet`](/docs/API.md). ```js import { View, StyleSheet } from 'react-sketchapp'; // inline props // plain JS object const style = { backgroundColor: 'hotPink', width: 300, } // StyleSheet const styles = StyleSheet.create({ foo: { backgroundColor: 'hotPink', width: 300, } }) ``` You can use variables in your styles just like a standard React application: ```javascript const colors = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', Peach: '#EFADA0', Pear: '#93DAAB', }; {Object.keys(colors).map(name => ( ))} ; ``` ================================================ FILE: docs/guides/universal-rendering.md ================================================ # Universal Rendering The `react-sketchapp` components have been architected to provide the same metaphors, layout system & interfaces as `react-native`, so there is less switching cost between platforms. However, it is also possible to render the _same code_ across multiple platforms. We call this _Universal Rendering_. The [`react-primitives`](https://github.com/lelandrichardson/react-primitives) project provides consistent primitive interfaces across platforms, and is the simplest way to achieve Universal Rendering. ## Setup React Primitives works out-of-the-box with `react-dom` & `react-native`, and `react-sketchapp` (when using `skpm`). Install `react-primitives` and its peer dependencies ```bash npm install --save react-primitives react react-dom react-native react-sketchapp ``` ## Creating your components Import base primitives from `react-primitives` rather than `react-sketchapp` / `react-native` — e.g. ```diff /** * components/Row.js * Define your component using platform-independent primitives */ import React from 'react'; - import { View, Text, StyleSheet } from 'react-sketchapp'; + import { View, Text, StyleSheet } from 'react-primitives'; const Row = props => { props.title } { props.subtitle } export default Row; ``` ## Importing existing components If you have a large existing React Native component library, you might enjoy using a `codemod` to automatically convert `react-native` imports to `react-primitives` — [a proof-of-concept `codemod` is provided on ASTExplorer](https://astexplorer.net/#/gist/68d1b3ae3ec7b0a088452a7d38643dc4/latest). ## Rendering Each platform will require an entry point with its respective `render` / registration call - e.g: ```js /** * dom-entry.js * Standard ReactDOM setup for the browser */ import React from 'react'; import { render } from 'react-dom'; import Row from './components/Row'; render(, document.getElementById('root')); ``` ```js /** * native-entry.js * Standard ReactNative setup */ import React from 'react'; import { AppRegistry } from 'react-native'; import Row from './components/Row'; AppRegistry.registerComponent('Row', () => Row); ``` ```js /** * sketch-entry.js * same setup as other examples */ import React from 'react'; import { render } from 'react-sketchapp'; import Row from './components/Row'; export default context => { render(, context.document.currentPage()); }; ``` React Primitives only provides components that make sense on every platform, so Sketch-specific concepts like `TextStyles` and `` should be imported from the main `react-sketchapp` package. You can mix-and-match them as necessary - e.g. ```js /** * sketch-entry.js * same setup as other examples */ import React from 'react'; import { Artboard, render } from 'react-sketchapp'; import Row from './components/Row'; // built with react-primitives export default context => { render( , context.document.currentPage(), ); }; ``` ================================================ FILE: docs/guides/using-skpm.md ================================================ # Using `skpm` as a build system Sketch allows arbitrary plugins written in [CocoaScript](http://developer.sketchapp.com/guides/cocoascript) to run. [`skpm`](https://github.com/skpm/skpm) is a utility to create, build and manage Sketch plugins. It takes care of transforming your JavaScript into CocoaScript and makes sure the context it is running in is as close as possible to what you are used to when writing JavaScript. ## Installation > Important: Node.JS > V6.x is a minimum requirement. ```bash npm install -g skpm ``` ## Usage ### Creating a new plugin ```bash skpm create my-plugin --template=airbnb/react-sketchapp ``` > A note on templates > > The purpose of skpm templates are to provide opinionated development tooling setups so that users can get started with actual plugin code as fast as possible. > > - [`airbnb/react-sketchapp`](https://github.com/airbnb/react-sketchapp) is a simple template to get started with `react-sketchapp` > > 💁 Tip: Any Github repo with a 'template' folder can be used as a custom template: `skpm create --template=/` ### Build the plugin Once the installation is done, you can run some commands inside the project folder: ```bash npm run build ``` To watch for changes: ```bash npm run watch ``` Additionally, if you wish to run the plugin every time it is built: ```bash npm run render ``` ### View the plugin's log To view the output of your `console.log`, you have a few different options: - Using the [sketch-dev-tools](https://github.com/skpm/sketch-dev-tools) - Open Console.app and look for the sketch logs - Look at the `~/Library/Logs/com.bohemiancoding.sketch3/Plugin Output.log` file Skpm provides a convenient way to do the latter: ```bash skpm log -f, -F The `-f` option causes tail to not stop when end of file is reached, but rather to wait for additional data to be appended to the input. [boolean] [default: "false"] --number, -n Shows `number` lines of the logs. [number] ``` ## Custom Configuration ### Babel To customize Babel, you have two options: - You may create a [`.babelrc`](https://babeljs.io/docs/usage/babelrc) file in your project's root directory. Any settings you define here will overwrite matching config-keys within skpm preset. For example, if you pass a "presets" object, it will replace & reset all Babel presets that skpm defaults to. - If you'd like to modify or add to the existing Babel config, you must use a `webpack.skpm.config.js` file. Visit the [`webpack`](#webpack) section for more info. ### `webpack` To customize `webpack` create `webpack.skpm.config.js` file which exports function that will change `webpack`'s config. ```js /** * Function that mutates original webpack config. * Supports asynchronous changes when promise is returned. * * @param {object} config - original webpack config. * @param {boolean} isPluginCommand - wether the config is for a plugin command or a resource **/ module.exports = function(config, isPluginCommand) { /** you can change config here **/ }; ``` ================================================ FILE: examples/.eslintrc ================================================ { "rules": { "import/no-unresolved": 0, "import/extensions": 0 } } ================================================ FILE: examples/.gitignore ================================================ **/*.sketchplugin ================================================ FILE: examples/basic-setup/README.md ================================================ # Basic setup ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/basic-setup cd basic-setup ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Basic skpm Example` Run with live reloading in Sketch, need a new sketch doc open ```bash npm run render ``` ## The idea behind the example [`skpm`](https://github.com/skpm/skpm) is the easiest way to build `react-sketchapp` projects - this is a minimal example of it in use. ![examples-basic](https://cloud.githubusercontent.com/assets/591643/24778192/1f0684ec-1ade-11e7-866b-b11bb60ac109.png) ================================================ FILE: examples/basic-setup/package.json ================================================ { "name": "basic-setup", "version": "1.0.0", "description": "", "skpm": { "main": "basic-setup.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.7.5" }, "dependencies": { "chroma-js": "^1.2.2", "prop-types": "^15.5.8", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" } } ================================================ FILE: examples/basic-setup/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Basic Setup", "identifier": "main", "script": "./my-command.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/basic-setup/src/my-command.js ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { render, Artboard, Text, View } from 'react-sketchapp'; import chroma from 'chroma-js'; // take a hex and give us a nice text color to put over it const textColor = (hex) => { const vsWhite = chroma.contrast(hex, 'white'); if (vsWhite > 4) { return '#FFF'; } return chroma(hex).darken(3).hex(); }; const Swatch = ({ name, hex }) => ( {name} {hex} ); const Color = { hex: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }; Swatch.propTypes = Color; const Document = ({ colors }) => ( {Object.keys(colors).map((color) => ( ))} ); Document.propTypes = { colors: PropTypes.objectOf(PropTypes.string).isRequired, }; export default () => { const colorList = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur Dark': '#24828F', Peach: '#EFADA0', 'Peach Dark': '#E37059', Pear: '#93DAAB', 'Pear Dark': '#2E854B', }; render(, context.document.currentPage()); }; ================================================ FILE: examples/basic-setup/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/basic-setup-typescript/.gitignore ================================================ node_modules .ts-compiled ================================================ FILE: examples/basic-setup-typescript/README.md ================================================ # Basic setup with Typescript This example was adapted from the [basic-setup example](../basic-setup). > **NOTE:** you may also use the typings _without_ using typescript if you editor supports it. [See here](../../docs/guides/community-provided-tooling.md). ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/basic-setup-typescript cd basic-setup-typescript ``` Install the dependencies ```bash npm install ``` Run with live reloading in Sketch, need a new sketch doc open. This will put both skpm and the Typescript compiler in watch mode: ```bash npm run render ``` To clean the `.ts-compiled` directory, you can run: ```bash npm run typescript:clean ``` ## How the typescript works This example compiles the typescript into javascript that can be used by `skpm`. The compiled typescript files get output into the `.ts-compiled` directory. The `manifest.json` of `skpm` then simply points to the compiled javascript. To get live re-loading working, use the typescript compiler in watch mode. Whenever you save a typescript file, the typescript compiler will output javascript to the `.ts-compiled` directory. Once `skpm` notices the javascript file in `.ts-compiled` changes, it will re-build and re-render. Here is a reference `tsconfig.json`: ```json { "compilerOptions": { "target": "es2015", "module": "es2015", "jsx": "react-native", "allowJs": true, "strict": true, "outDir": "./.ts-compiled", "rootDir": "./src", "allowSyntheticDefaultImports": true, "moduleResolution": "node" }, "include": [ "./src/**/*" ] } ``` ================================================ FILE: examples/basic-setup-typescript/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Basic Setup with Typescript", "identifier": "main", "script": "./.ts-compiled/my-command.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/basic-setup-typescript/package.json ================================================ { "name": "basic-setup-typescript", "version": "1.0.0", "description": "", "skpm": { "main": "basic-setup.sketchplugin", "manifest": "./manifest.json" }, "scripts": { "build": "npm run typescript:once && skpm-build", "watch": "skpm-build --watch & npm run typescript", "render": "skpm-build --watch --run & npm run typescript", "render:once": "npm run typescript:once && skpm-build --run", "postinstall": "npm run build && skpm-link", "typescript": "tsc --watch", "typescript:once": "tsc", "typescript:clean": "rm -rf ./.ts-compiled" }, "author": "Jon Gold ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.4.0", "@types/chroma-js": "^1.3.3", "typescript": "^3.7.2" }, "dependencies": { "chroma-js": "^1.2.2", "prop-types": "^15.5.8", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" } } ================================================ FILE: examples/basic-setup-typescript/src/my-command.tsx ================================================ import * as React from 'react'; import sketch from 'sketch'; import { render, Artboard, Text, View } from 'react-sketchapp'; import chroma from 'chroma-js'; // take a hex and give us a nice text color to put over it const textColor = (hex: string) => { const vsWhite = chroma.contrast(hex, 'white'); if (vsWhite > 4) { return '#FFF'; } return chroma(hex).darken(3).hex(); }; interface SwatchProps { name: string; hex: string; } const Swatch = ({ name, hex }: SwatchProps) => ( {name} {hex} ); interface DocumentProps { colors: { [key: string]: string }; } const Document = ({ colors }: DocumentProps) => ( {Object.keys(colors).map((color) => ( ))} ); export default () => { const colorList = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur Dark': '#24828F', Peach: '#EFADA0', 'Peach Dark': '#E37059', Pear: '#93DAAB', 'Pear Dark': '#2E854B', 'TypeScript Blue': '#007ACC', }; render(, sketch.getSelectedDocument().selectedPage); }; ================================================ FILE: examples/basic-setup-typescript/src/types/sketch.d.ts ================================================ declare module 'sketch'; ================================================ FILE: examples/basic-setup-typescript/tsconfig.json ================================================ { "compilerOptions": { "target": "es2015", "module": "es2015", "jsx": "react-native", "allowJs": true, "strict": true, "outDir": "./.ts-compiled", "rootDir": "./src", "allowSyntheticDefaultImports": true, "moduleResolution": "node" }, "include": [ "./src/**/*" ] } ================================================ FILE: examples/basic-setup-typescript/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/basic-svg/README.md ================================================ # Basic SVG example ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/basic-svg cd basic-svg ``` Install the dependencies ```bash npm install ``` Run with live reloading in Sketch, need a new sketch doc open ```bash npm run render ``` Or, to install as a Sketch plugin: ```bash npm run build npm run link-plugin ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Basic SVG Example` ## The idea behind the example [`skpm`](https://github.com/sketch-pm/skpm) is the easiest way to build `react-sketchapp` projects - this is a minimal example of it in use. ![examples-basic](https://cloud.githubusercontent.com/assets/591643/24778192/1f0684ec-1ade-11e7-866b-b11bb60ac109.png) ================================================ FILE: examples/basic-svg/package.json ================================================ { "name": "basic-svg", "version": "1.0.0", "description": "", "skpm": { "main": "basic-svg.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.7.5" }, "dependencies": { "prop-types": "^15.5.8", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" } } ================================================ FILE: examples/basic-svg/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Basic SVG", "identifier": "main", "script": "./my-command.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/basic-svg/src/my-command.js ================================================ import * as React from 'react'; import { render, Artboard, Svg } from 'react-sketchapp'; const Document = () => ( ); export default () => { render(, context.document.currentPage()); }; ================================================ FILE: examples/basic-svg/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/colors/README.md ================================================ # Generative Colors w/ chroma-js ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/colors cd colors ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Generative Colors` Run with live reloading in Sketch, need a new sketch doc open ```bash npm run render ``` ## The idea behind the example Calculating color scales is pretty tricky in Sketch - have fun with it! ![examples-colors](https://cloud.githubusercontent.com/assets/591643/24778153/efc76cdc-1add-11e7-93dd-0351f8f428c0.png) ================================================ FILE: examples/colors/package.json ================================================ { "name": "colors", "version": "1.0.0", "private": true, "skpm": { "main": "colors.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "chroma-js": "^1.2.2", "prop-types": "^15.5.8", "ramda": "^0.23.0", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2", "webpack-shell-plugin": "^0.5.0" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/colors/src/main.js ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { render, StyleSheet, View } from 'react-sketchapp'; import chroma from 'chroma-js'; import { times } from 'ramda'; const styles = StyleSheet.create({ container: { width: 480, height: 480, flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', }, }); const Document = ({ colors, steps }) => { const color = chroma.scale(colors); return ( {times((i) => color(i / steps).hex(), steps).map((val, i) => ( ))} ); }; Document.propTypes = { colors: PropTypes.arrayOf(PropTypes.string), steps: PropTypes.number, }; export default () => { render( , context.document.currentPage(), ); }; ================================================ FILE: examples/colors/src/manifest.json ================================================ { "name": "colors", "author": "Jon Gold", "version": 0.1, "compatibleVersion": 1, "bundleVersion": 1, "disableCocoaScriptPreprocessor": true, "commands": [ { "name": "react-sketchapp: Generative Colors", "identifier": "main", "script": "main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/colors/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/emotion/package.json ================================================ { "name": "emotion-example", "version": "1.0.0", "skpm": { "main": "emotion.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Nitin Tulswani ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.4.3" }, "dependencies": { "emotion-primitives": "^1.0.0-beta.6", "react": "^16.4.1", "react-primitives": "^0.6.0", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" } } ================================================ FILE: examples/emotion/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: emotion", "identifier": "main", "script": "./my-command.js" } ], "menu": { "isRoot": true, "items": ["main"] } } ================================================ FILE: examples/emotion/src/my-command.js ================================================ import React from 'react'; import emotion from 'emotion-primitives'; import { render } from 'react-sketchapp'; const Container = emotion.View` display: flex; justify-content: center; align-items: center; margin: 50px; border: 5px solid red; background-color: ${(props) => props.theme.backgroundColor} `; const Description = emotion.Text` color: hotpink; `; const Image = emotion.Image` padding: 40px; `; const emotionLogo = 'https://avatars3.githubusercontent.com/u/31557565?s=400&v=4'; class App extends React.Component { render() { return ( Emotion Primitives ); } } export default () => { render(, context.document.currentPage()); }; ================================================ FILE: examples/emotion/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/form-validation/README.md ================================================ # Form Validation ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/form-validation cd form-validation ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Form Validation` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example `react-sketchapp` makes it simple to render all potential states of a web component to sketch. ![examples-form-validation](https://cloud.githubusercontent.com/assets/1606253/25585002/5cff9264-2e90-11e7-80dc-101f10ecad6d.png) ================================================ FILE: examples/form-validation/package.json ================================================ { "name": "form-validation", "version": "1.0.0", "private": true, "skpm": { "main": "form-validation.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link", "web": "react run src/web.js" }, "author": "Lloyd Wheeler ", "license": "MIT", "dependencies": { "prop-types": "^15.5.8", "react": "^16.3.2", "react-dom": "^16.3.2", "react-native": "^0.42.3", "react-primitives": "^0.6.0", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "extract-text-webpack-plugin": "^2.1.0", "nwb": "^0.15.6", "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/form-validation/src/components/Button.js ================================================ import * as React from 'react'; import { Text, View } from 'react-primitives'; import { spacing, colors, fontFamily } from '../designSystem'; const buttonStyle = { borderRadius: 3, boxSizing: 'border-box', color: colors.White, cursor: 'pointer', padding: spacing.Medium, width: 300, }; const textStyle = { color: colors.White, fontFamily, fontWeight: 'bold', textAlign: 'center', }; const Button = ({ label, backgroundColor }) => ( {label} ); export default Button; ================================================ FILE: examples/form-validation/src/components/Register.js ================================================ import * as React from 'react'; import { View, Text, StyleSheet } from 'react-primitives'; import { spacing, colors, typeRamp, fontFamily } from '../designSystem'; import TextBox from './TextBox'; import StrengthMeter from './StrengthMeter'; import Button from './Button'; const styles = StyleSheet.create({ register: { backgroundColor: colors.LightGrey, padding: spacing.Large, boxSizing: 'border-box', }, heading: { color: colors.Purple, fontSize: typeRamp.Medium, fontFamily, fontWeight: 'bold', textAlign: 'center', marginBottom: spacing.Medium, width: 300, }, }); const Register = ({ session }) => ( Register an Account ); Register.defaultProps = { session: { email: '', password: '', }, }; export default Register; ================================================ FILE: examples/form-validation/src/components/Space.js ================================================ import * as React from 'react'; import { View } from 'react-primitives'; const Space = ({ h, v, children }) => ( {children} ); export default Space; ================================================ FILE: examples/form-validation/src/components/StrengthMeter.js ================================================ import * as React from 'react'; import { View, Text } from 'react-primitives'; import { colors, fontFamily, spacing, typeRamp } from '../designSystem'; const strengths = { short: { width: 75, label: 'Too short', backgroundColor: colors.Rose, }, fair: { width: 150, label: 'Fair', backgroundColor: colors.Yellow, }, good: { width: 225, label: 'Good', backgroundColor: colors.Yellow, }, strong: { width: 300, label: 'Strong', backgroundColor: colors.Green, }, }; const styles = { meter: { boxSizing: 'border-box', height: 5, width: 300, backgroundColor: '#ddd', marginTop: spacing.Medium, marginBottom: spacing.Large, borderRadius: 5, }, innerMeter: { boxSizing: 'border-box', height: 5, borderRadius: 5, }, meterLabel: { fontFamily, textAlign: 'right', width: 300, fontSize: typeRamp.Small, marginTop: 5, }, }; const passwordStrength = (password) => { // Faux password checking if (password.length <= 6) { return 'short'; } if (password.length <= 9) { return 'fair'; } if (password.length <= 12) { return 'good'; } return 'strong'; }; const StrengthMeter = ({ password }) => ( {password.length > 0 && ( {strengths[passwordStrength(password)].label} )} ); export default StrengthMeter; ================================================ FILE: examples/form-validation/src/components/TextBox/index.js ================================================ import React, { Component } from 'react'; import styles from './style'; class TextBox extends Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { value: this.props.value, }; } handleChange(event) { this.setState({ value: event.target.value }); } render() { return (
{this.props.children && React.cloneElement(this.props.children, { password: this.state.value, })}
); } } export default TextBox; ================================================ FILE: examples/form-validation/src/components/TextBox/index.sketch.js ================================================ import * as React from 'react'; import { View, Text } from 'react-primitives'; import styles from './style'; type Props = { label: string, value: string, children?: React$Element, }; const TextBox = ({ label, value, children }: Props) => ( {label} {value} {children} ); export default TextBox; ================================================ FILE: examples/form-validation/src/components/TextBox/style.js ================================================ import { colors, spacing, fontFamily, typeRamp } from '../../designSystem'; export default { formElement: { marginBottom: spacing.Medium, }, label: { display: 'block', fontFamily, marginBottom: spacing.Small, fontSize: typeRamp.Medium - 2, }, textbox: { boxSizing: 'border-box', borderWidth: 1, borderStyle: 'solid', borderColor: colors.Grey, backgroundColor: colors.White, fontFamily, fontSize: typeRamp.Medium, lineHeight: typeRamp.Medium, padding: spacing.Medium, width: 300, }, }; ================================================ FILE: examples/form-validation/src/data.js ================================================ export default [ { email: 'john.hornsby@example.com', password: '', }, { email: 'john.hornsby@example.com', password: 'hello', }, { email: 'john.hornsby@example.com', password: '!H3ll0!', }, { email: 'john.hornsby@example.com', password: 'IL0v3ToasT!', }, { email: 'john.hornsby@example.com', password: 'IRea11yL0v3ToasT!', }, ]; ================================================ FILE: examples/form-validation/src/designSystem.js ================================================ export const colors = { Purple: '#5700A2', Yellow: '#BB9A05', Orange: '#fd6134', Rose: '#ff4289', Green: '#005b4c', Black: '#222223', LightGrey: '#eeeeee', Grey: '#cccccc', White: '#ffffff', }; export const spacing = { xSmall: 4, Small: 8, Medium: 16, Large: 32, xLarge: 64, }; export const typeRamp = { xSmall: 7, Small: 12, Medium: 16, Large: 24, xLarge: 36, }; export const typography = { Heading: { fontSize: typeRamp.Large, textAlign: 'center', marginBottom: spacing.Large, }, }; export const fontFamily = 'Helvetica'; export default { colors, spacing, typeRamp, typography, fontFamily, }; ================================================ FILE: examples/form-validation/src/main.js ================================================ import * as React from 'react'; import { render, View } from 'react-sketchapp'; import { Text } from 'react-primitives'; import { typography, spacing } from './designSystem'; import DATA from './data'; import Register from './components/Register'; import Space from './components/Space'; const Page = ({ sessions }) => ( Form Validation w/ DOM elements and React Primitives {sessions.map((session) => ( ))} ); export default () => { render(, context.document.currentPage()); }; ================================================ FILE: examples/form-validation/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Form Validation", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/form-validation/src/web.js ================================================ import * as React from 'react'; import { typography, fontFamily } from './designSystem'; import Register from './components/Register'; const styles = { containerStyle: { width: 364, marginLeft: 'auto', marginRight: 'auto', }, }; export default () => (

Form Validation w/ DOM elements and React Primitives. Type a password! 👀

); ================================================ FILE: examples/form-validation/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/foursquare-maps/.eslintrc ================================================ { "env": { "browser": true, } } ================================================ FILE: examples/foursquare-maps/README.md ================================================ # Foursquare + Google Maps ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/foursquare-maps cd foursquare-maps ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Foursquare + Google Maps` ### Run it in Sketch Run with live reloading in Sketch ```bash npm run render ``` ### Run it in your browser ```bash npm run web ``` Open a browser to `http://localhost:3000` ## The idea behind the example Creating maps with live data into Sketch is notoriously difficult — until now ;) This example is created with `react-primitives` and renders simultaneously to Sketch & Web — maps are provided by [react-primitives-google-static-map](https://www.npmjs.com/package/react-primitives-google-static-map). ![foursquare-maps](https://cloud.githubusercontent.com/assets/591643/25052095/f666928e-2104-11e7-805c-a3c73ffcabcb.png) ================================================ FILE: examples/foursquare-maps/package.json ================================================ { "name": "foursquare-maps", "version": "1.0.0", "private": true, "skpm": { "main": "foursquare-maps.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "web": "react run src/web.js", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "jquery-param": "^0.2.0", "nwb": "^0.15.6", "prop-types": "^15.5.8", "react": "^16.3.2", "react-dom": "^16.3.2", "react-native": "^0.42.3", "react-primitives": "^0.6.0", "react-primitives-google-static-map": "^1.0.1", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/foursquare-maps/src/App.js ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import Map from 'react-primitives-google-static-map'; import { StyleSheet, Text, View } from 'react-primitives'; const styles = StyleSheet.create({ container: { width: 375, height: 667, backgroundColor: '#fefefe', borderWidth: 2, borderColor: '#dfba69', borderRadius: 4, overflowY: 'scroll', }, text: { fontFamily: 'Helvetica', fontSize: 24, lineHeight: 24, color: '#dfba69', textAlign: 'center', }, rowWrapper: { padding: 16, backgroundColor: '#FFF', borderBottomWidth: 2, borderBottomColor: '#dfba69', }, rowTitle: { color: '#dfba69', fontSize: 18, // lineHeight: 27, fontWeight: 'bold', fontFamily: 'GT America', }, rowSubtitle: { color: '#dfba69', fontSize: 14, // lineHeight: 18, fontFamily: 'GT America', }, }); const LatLong = PropTypes.shape({ latitude: PropTypes.string, longitude: PropTypes.string, }); const Venue = { name: PropTypes.string, location: PropTypes.shape({ address: PropTypes.string, }), }; const Row = ({ name, location }) => ( {name} {location.address} ); Row.propTypes = Venue; const App = ({ center, venues }) => { const pins = venues.map((v) => ({ latitude: v.location.lat, longitude: v.location.lng, })); return ( {venues.map((v) => ( ))} ); }; App.propTypes = { center: LatLong, venues: PropTypes.arrayOf(PropTypes.shape(Venue)), }; export default App; ================================================ FILE: examples/foursquare-maps/src/getVenues.js ================================================ import param from 'jquery-param'; export default () => { const query = 'burger'; const latitude = '37.773972'; const longitude = '-122.431297'; const params = param({ v: '20161016', ll: [latitude, longitude].join(','), query, limit: 15, intent: 'checkin', client_id: 'BCUJZ2MSKUWJC2Q5HVIYZLHRWGFJ2OFPKPLBP1NOBNR3VW5R', client_secret: 'Q10HUP5APBQOYNTPABSH4CSKRGEAI2CXIYULYGG0EZYUUWUZ', }); return fetch(`https://api.foursquare.com/v2/venues/search?${params}`) .then((res) => res.json()) .then((data) => ({ venues: data.response.venues, latitude, longitude, query, })); }; ================================================ FILE: examples/foursquare-maps/src/main.js ================================================ import * as React from 'react'; import { Artboard, render } from 'react-sketchapp'; import App from './App'; import getVenues from './getVenues'; export default () => { getVenues().then(({ venues, latitude, longitude }) => { render( , context.document.currentPage(), ); }); }; ================================================ FILE: examples/foursquare-maps/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Foursquare + Google Maps", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/foursquare-maps/src/web.js ================================================ import * as React from 'react'; import { render } from 'react-dom'; import App from './App'; import getVenues from './getVenues'; getVenues().then(({ venues, latitude, longitude }) => { render(, document.getElementById('app')); }); ================================================ FILE: examples/foursquare-maps/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/glamorous/README.md ================================================ # glamorous 💄 ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/glamorous cd glamorous ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: glamorous` ### Run it in Sketch Run with live reloading in Sketch ```bash npm run render ``` ================================================ FILE: examples/glamorous/package.json ================================================ { "name": "glamorous-example", "version": "1.0.0", "description": "", "skpm": { "main": "glamorous.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Nitin Tulswani ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.4.0" }, "dependencies": { "chroma-js": "^1.3.4", "glamorous-primitives": "^2.1.0", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" } } ================================================ FILE: examples/glamorous/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: glamorous", "identifier": "main", "script": "./my-command.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/glamorous/src/my-command.js ================================================ import * as React from 'react'; import glamorous from 'glamorous-primitives'; import { render } from 'react-sketchapp'; const Container = glamorous.view({ display: 'flex', justifyContent: 'center', alignItems: 'center', }); const Image = glamorous.image({ width: 400, height: 400, }); const Description = glamorous.text({ fontSize: 35, padding: 40, color: '#a4a4c1', }); class App extends React.Component { render() { return ( Maintainable CSS with React ); } } export default () => { render(, context.document.currentPage()); }; ================================================ FILE: examples/glamorous/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/profile-cards/README.md ================================================ # Profile Cards ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/profile-cards cd profile-cards ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Profile Cards` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example `react-sketchapp` makes it simple to compose components and see how they perform with real data. ![examples-profile-cards](https://cloud.githubusercontent.com/assets/591643/24778173/0dd7c03c-1ade-11e7-8bad-1ad51fe1033e.png) ================================================ FILE: examples/profile-cards/package.json ================================================ { "name": "profile-cards", "private": true, "skpm": { "main": "profile-cards.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/profile-cards/src/components/Profile.js ================================================ import * as React from 'react'; import { Image, View, Text, StyleSheet } from 'react-sketchapp'; import { colors, fonts, spacing } from '../designSystem'; const styles = StyleSheet.create({ container: { backgroundColor: colors.Haus, padding: 20, width: 260, }, avatar: { height: 220, resizeMode: 'contain', marginBottom: 20, borderRadius: 10, }, title: fonts['Title 2'], subtitle: fonts['Title 3'], body: fonts.Body, }); const Avatar = ({ url }) => ; const Title = ({ children }) => {children}; const Subtitle = ({ children }) => {children}; const Body = ({ children }) => {children}; const Profile = props => ( {props.user.name} {`@${props.user.screen_name}`} {props.user.description} {props.user.location} {props.user.url} ); export default Profile; ================================================ FILE: examples/profile-cards/src/components/Space.js ================================================ import * as React from 'react'; import { View } from 'react-sketchapp'; const Space = ({ h, v, children }) => ( {children} ); export default Space; ================================================ FILE: examples/profile-cards/src/designSystem.js ================================================ export const colors = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur a11y': '#24828F', Peach: '#EFADA0', 'Peach a11y': '#E37059', Pear: '#93DAAB', 'Pear a11y': '#2E854B', }; const typeSizes = [80, 48, 36, 24, 20, 16]; export const spacing = 16; const fontFamilies = { display: 'Helvetica', body: 'Georgia', }; const fontWeights = { regular: 'regular', bold: 'bold', }; export const fonts = { Headline: { color: colors.Night, fontSize: typeSizes[0], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 80, }, 'Title 1': { color: colors.Night, fontSize: typeSizes[2], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 48, }, 'Title 2': { color: colors.Night, fontSize: typeSizes[3], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 36, }, 'Title 3': { color: colors.Night, fontSize: typeSizes[4], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, }, Body: { color: colors.Night, fontSize: typeSizes[5], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, marginBottom: 24, }, }; export default { colors, fonts, spacing, }; ================================================ FILE: examples/profile-cards/src/main.js ================================================ import * as React from 'react'; import { render, Text, View } from 'react-sketchapp'; import { fonts, spacing } from './designSystem'; import Profile from './components/Profile'; import Space from './components/Space'; const Page = ({ users }) => ( Profile Cards {users.map((user) => ( ))} ); export default () => { const DATA = [ { screen_name: 'mxstbr', name: 'Max Stoiber', description: '⚛️ Makes styled-components, react-boilerplate, @KeystoneJS and CarteBlanche. ✌ Open source developer @thethinkmill. ☕ Speciality coffee geek, skier, traveller.', location: 'Vienna, Austria', url: 'mxstbr.com', profile_image_url: 'https://pbs.twimg.com/profile_images/763033229993574400/6frGyDyA_400x400.jpg', }, { name: '- ̗̀Jackie ̖́-', screen_name: 'jackiesaik', description: 'Graphic designer, never won a spelling be. Toronto on weekdays. Go Home Lake on weekends. ╮ (. ● ᴗ ●.) ╭', location: 'Toronto, ON', url: 'cargocollective.com/jackiesaik', profile_image_url: 'https://pbs.twimg.com/profile_images/895665264464764930/7Mb3QtEB_400x400.jpg', }, { screen_name: 'jongold', name: 'kerning man', description: 'an equal command of technology and form • functional programming (oc)cultist • design tools @airbnbdesign', location: 'California', url: 'weirdwideweb.jon.gold', profile_image_url: 'https://pbs.twimg.com/profile_images/833785170285178881/loBb32g3.jpg', }, ]; render(, context.document.currentPage()); }; ================================================ FILE: examples/profile-cards/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Profile Cards", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/profile-cards/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/profile-cards-graphql/README.md ================================================ # Profile Cards w/ GraphQL ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/profile-cards-graphql cd profile-cards-graphql ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Profile Cards w/ GraphQL` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example Building on the [Profile Cards example](../profile-cards), it's simple to fetch real data for your mockups with GraphQL! ![examples-profile-cards-gql](https://cloud.githubusercontent.com/assets/591643/24778175/0dda21d8-1ade-11e7-9545-13b472263ff6.png) ================================================ FILE: examples/profile-cards-graphql/package.json ================================================ { "name": "profile-cards-gql", "version": "1.0.0", "skpm": { "main": "profile-cards-gql.sketchplugin", "manifest": "src/manifest.json" }, "private": true, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "apollo-cache-inmemory": "^1.1.11", "apollo-client": "^2.2.7", "apollo-link-http": "^1.5.3", "graphql": "^0.13.2", "graphql-tag": "^2.4.0", "react": "^16.3.2", "react-apollo": "^2.1.0", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/profile-cards-graphql/src/components/Profile.js ================================================ import * as React from 'react'; import { Image, View, Text, StyleSheet } from 'react-sketchapp'; import { colors, fonts, spacing } from '../designSystem'; const styles = StyleSheet.create({ container: { backgroundColor: colors.Haus, padding: 20, width: 260, }, avatar: { height: 220, resizeMode: 'contain', marginBottom: 20, borderRadius: 10, }, title: fonts['Title 2'], subtitle: fonts['Title 3'], body: fonts.Body, }); const Avatar = ({ url }) => ; const Title = ({ children }) => {children}; const Subtitle = ({ children }) => {children}; const Body = ({ children }) => {children}; const Profile = props => ( {props.user.name} @{props.user.screenname} {props.user.description} {props.user.location} {props.user.url} ); export default Profile; ================================================ FILE: examples/profile-cards-graphql/src/components/Space.js ================================================ import * as React from 'react'; import { View } from 'react-sketchapp'; const Space = ({ h, v, children }) => ( {children} ); export default Space; ================================================ FILE: examples/profile-cards-graphql/src/designSystem.js ================================================ export const colors = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur a11y': '#24828F', Peach: '#EFADA0', 'Peach a11y': '#E37059', Pear: '#93DAAB', 'Pear a11y': '#2E854B', }; const typeSizes = [80, 48, 36, 24, 20, 16]; export const spacing = 16; const fontFamilies = { display: 'Helvetica', body: 'Georgia', }; const fontWeights = { regular: 'regular', bold: 'bold', }; export const fonts = { Headline: { color: colors.Night, fontSize: typeSizes[0], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 80, }, 'Title 1': { color: colors.Night, fontSize: typeSizes[2], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 48, }, 'Title 2': { color: colors.Night, fontSize: typeSizes[3], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 36, }, 'Title 3': { color: colors.Night, fontSize: typeSizes[4], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, }, Body: { color: colors.Night, fontSize: typeSizes[5], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, marginBottom: 24, }, }; export default { colors, fonts, spacing, }; ================================================ FILE: examples/profile-cards-graphql/src/main.js ================================================ import * as React from 'react'; import { render, Text, View } from 'react-sketchapp'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { graphql, ApolloProvider } from 'react-apollo'; import gql from 'graphql-tag'; import { fonts, spacing } from './designSystem'; import Profile from './components/Profile'; import Space from './components/Space'; const GRAPHQL_ENDPOINT = 'https://api.graph.cool/simple/v1/cj09zm1k4jcpc0115ecsoc1k4'; const client = new ApolloClient({ link: new HttpLink({ uri: GRAPHQL_ENDPOINT }), ssrMode: true, cache: new InMemoryCache(), }); const QUERY = gql` { allProfiles { screenname name description location url profileImageUrl } } `; const props = ({ data }) => (data.loading ? { users: [] } : { users: data.allProfiles }); const withUsers = graphql(QUERY, { props }); const Page = ({ users }) => ( Profile Cards w/ GraphQL {users && ( {users.map((user) => ( ))} )} ); const PageWithUsers = withUsers(Page); const App = () => ( ); export default () => { client .query({ query: QUERY }) .then(() => render(, context.document.currentPage())) .catch(console.log); }; ================================================ FILE: examples/profile-cards-graphql/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Profile Cards w/ GraphQL", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/profile-cards-graphql/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/profile-cards-primitives/README.md ================================================ # Profile Cards w/ React Primitives ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/profile-cards-primitives cd profile-cards-primitives ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Profile Cards w/ Primitives` ### Run it in Sketch Run with live reloading in Sketch ```bash npm run render ``` ### Run it in your browser ```bash npm run web ``` Open a browser to `http://localhost:3000` ## The idea behind the example `react-primitives` provides a powerful way to build platform-independent components. This is a simple example of rendering to web & Sketch simultaneously. ![examples-primitives](https://cloud.githubusercontent.com/assets/591643/24778174/0dd9d214-1ade-11e7-9372-fa8b578a0b48.png) ================================================ FILE: examples/profile-cards-primitives/nwb.config.js ================================================ module.exports = { webpack: { resolve: { // look for .web.js first extensions: ['.web.js', '.js', '.json'], }, }, }; ================================================ FILE: examples/profile-cards-primitives/package.json ================================================ { "name": "profile-cards-primitives", "private": true, "skpm": { "main": "profile-cards-primitives.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link", "web": "react run src/web.js" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "nwb": "^0.15.6", "react": "^16.3.2", "react-dom": "^15.4.2", "react-native": "^0.42.3", "react-primitives": "^0.6.0", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/profile-cards-primitives/src/components/Profile.js ================================================ import * as React from 'react'; import { Image, View, Text, StyleSheet } from 'react-primitives'; import { colors, fonts, spacing } from '../designSystem'; const styles = StyleSheet.create({ container: { backgroundColor: colors.Haus, padding: 20, width: 260, }, avatar: { height: 220, resizeMode: 'contain', marginBottom: 20, borderRadius: 10, }, title: fonts['Title 2'], subtitle: fonts['Title 3'], body: fonts.Body, }); const Avatar = ({ url }) => ; const Title = ({ children }) => {children}; const Subtitle = ({ children }) => {children}; const Body = ({ children }) => {children}; const Profile = (props) => ( {props.user.name} {`@${props.user.screen_name}`} {props.user.description} {props.user.location} {props.user.url} ); export default Profile; ================================================ FILE: examples/profile-cards-primitives/src/components/Space.js ================================================ import * as React from 'react'; import { View } from 'react-primitives'; const Space = ({ h, v, children }) => ( {children} ); export default Space; ================================================ FILE: examples/profile-cards-primitives/src/data.js ================================================ export default [ { screen_name: 'mxstbr', name: 'Max Stoiber', description: '⚛️ Makes styled-components, react-boilerplate, @KeystoneJS and CarteBlanche. ✌ Open source developer @thethinkmill. ☕ Speciality coffee geek, skier, traveller.', location: 'Vienna, Austria', url: 'mxstbr.com', profile_image_url: 'https://pbs.twimg.com/profile_images/763033229993574400/6frGyDyA_400x400.jpg', }, { name: '- ̗̀Jackie ̖́-', screen_name: 'jackiesaik', description: 'Graphic designer, never won a spelling be. Toronto on weekdays. Go Home Lake on weekends. ╮ (. ● ᴗ ●.) ╭', location: 'Toronto, ON', url: 'cargocollective.com/jackiesaik', profile_image_url: 'https://pbs.twimg.com/profile_images/895665264464764930/7Mb3QtEB_400x400.jpg', }, { screen_name: 'jongold', name: 'kerning man', description: 'an equal command of technology and form • functional programming (oc)cultist • design tools @airbnbdesign', location: 'California', url: 'weirdwideweb.jon.gold', profile_image_url: 'https://pbs.twimg.com/profile_images/833785170285178881/loBb32g3.jpg', }, ]; ================================================ FILE: examples/profile-cards-primitives/src/designSystem.js ================================================ export const colors = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur a11y': '#24828F', Peach: '#EFADA0', 'Peach a11y': '#E37059', Pear: '#93DAAB', 'Pear a11y': '#2E854B', }; const typeSizes = [80, 48, 36, 24, 20, 16]; export const spacing = 16; const fontFamilies = { display: 'Helvetica', body: 'Georgia', }; const fontWeights = { regular: 'regular', bold: 'bold', }; export const fonts = { Headline: { color: colors.Night, fontSize: typeSizes[0], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 80, }, 'Title 1': { color: colors.Night, fontSize: typeSizes[2], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 48, }, 'Title 2': { color: colors.Night, fontSize: typeSizes[3], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 36, }, 'Title 3': { color: colors.Night, fontSize: typeSizes[4], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, }, Body: { color: colors.Night, fontSize: typeSizes[5], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, marginBottom: 24, }, }; export default { colors, fonts, spacing, }; ================================================ FILE: examples/profile-cards-primitives/src/main.js ================================================ import * as React from 'react'; import { render } from 'react-sketchapp'; import { Text, View } from 'react-primitives'; import { fonts, spacing } from './designSystem'; import Profile from './components/Profile'; import Space from './components/Space'; import DATA from './data'; const Page = ({ users }) => ( Profile Cards w/ React Primitives {users.map((user) => ( ))} ); export default () => { render(, context.document.currentPage()); }; ================================================ FILE: examples/profile-cards-primitives/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Profile Cards w/ Primitives", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/profile-cards-primitives/src/web.js ================================================ import * as React from 'react'; import Profile from './components/Profile'; import Space from './components/Space'; import { spacing } from './designSystem'; import DATA from './data'; /* * is defined with platform-independent components * from react-primitives. We can use it in our web UI, and * continue to use primitives, or mix them with DOM elements */ export default () => (

Cross-platform components!

<Profile /> is defined with platform-independent components from react-primitives. We can use it in our web UI, and continue to use primitives, or mix them with DOM elements

{DATA.map((user) => ( ))}
); ================================================ FILE: examples/profile-cards-primitives/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/profile-cards-react-with-styles/README.md ================================================ # Profile Cards w/ react-with-styles ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/profile-cards-react-with-styles cd profile-cards-react-with-styles ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Profile Cards` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example Use `react-sketchapp` with [`react-with-styles`](https://github.com/airbnb/react-with-styles) — a library for writing CSS-in-JS without coupling to specific implementations. ![examples-profile-cards](https://cloud.githubusercontent.com/assets/591643/24778173/0dd7c03c-1ade-11e7-8bad-1ad51fe1033e.png) ================================================ FILE: examples/profile-cards-react-with-styles/package.json ================================================ { "name": "profile-cards-react-with-styles", "private": true, "skpm": { "main": "profile-cards-react-with-styles.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2", "react-with-styles": "^1.4.0" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/profile-cards-react-with-styles/src/components/Profile.js ================================================ import * as React from 'react'; import { Image, View, Text } from 'react-sketchapp'; import { css, withStyles } from '../withStyles'; const Profile = ({ user, styles }) => ( {user.name} {`@${user.screen_name}`} {user.description} {user.location} {user.url} ); export default withStyles(({ colors, fonts, spacing }) => ({ container: { backgroundColor: colors.Haus, padding: spacing, width: 260, marginRight: spacing, }, avatar: { height: 220, resizeMode: 'contain', marginBottom: spacing * 2, borderRadius: 10, }, titleWrapper: { marginBottom: spacing, }, title: { ...fonts['Title 2'] }, subtitle: { ...fonts['Title 3'] }, body: { ...fonts.Body }, }))(Profile); ================================================ FILE: examples/profile-cards-react-with-styles/src/main.js ================================================ import * as React from 'react'; import { render, Text, View } from 'react-sketchapp'; import Profile from './components/Profile'; import { css, withStyles } from './withStyles'; const Title = withStyles(({ fonts }) => ({ titleText: fonts['Title 1'], }))(({ children, styles }) => {children}); const Page = ({ users }) => ( Profile Cards w/ react-with-styles {users.map((user) => ( ))} ); export default () => { const DATA = [ { screen_name: 'jaredpalmer', name: 'Jared Palmer', description: 'Engineer @PalmerGroupHQ', location: 'New York, NY', url: 'github.com/jaredpalmer', profile_image_url: 'https://pbs.twimg.com/profile_images/662984079638405120/Y6oncSaf.jpg', }, { name: '- ̗̀Jackie ̖́-', screen_name: 'jackiesaik', description: 'Graphic designer, never won a spelling be. Toronto on weekdays. Go Home Lake on weekends. ╮ (. ● ᴗ ●.) ╭', location: 'Toronto, ON', url: 'cargocollective.com/jackiesaik', profile_image_url: 'https://pbs.twimg.com/profile_images/895665264464764930/7Mb3QtEB_400x400.jpg', }, { screen_name: 'jongold', name: 'kerning man', description: 'an equal command of technology and form • functional programming (oc)cultist • design tools @airbnbdesign', location: 'California', url: 'weirdwideweb.jon.gold', profile_image_url: 'https://pbs.twimg.com/profile_images/833785170285178881/loBb32g3.jpg', }, ]; render(, context.document.currentPage()); }; ================================================ FILE: examples/profile-cards-react-with-styles/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Profile Cards w/ react-with-styles", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/profile-cards-react-with-styles/src/theme.js ================================================ export const colors = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur a11y': '#24828F', Peach: '#EFADA0', 'Peach a11y': '#E37059', Pear: '#93DAAB', 'Pear a11y': '#2E854B', }; const typeSizes = [80, 48, 36, 24, 20, 16]; export const spacing = 16; const fontFamilies = { display: 'Helvetica', body: 'Georgia', }; const fontWeights = { regular: 'regular', bold: 'bold', }; export const fonts = { Headline: { color: colors.Night, fontSize: typeSizes[0], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 80, }, 'Title 1': { color: colors.Night, fontSize: typeSizes[2], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 48, }, 'Title 2': { color: colors.Night, fontSize: typeSizes[3], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 36, }, 'Title 3': { color: colors.Night, fontSize: typeSizes[4], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, }, Body: { color: colors.Night, fontSize: typeSizes[5], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, marginBottom: 24, }, }; export default { colors, fonts, spacing, }; ================================================ FILE: examples/profile-cards-react-with-styles/src/types.js ================================================ export type User = { screen_name: string, name: string, description: string, profile_image_url: string, location: string, url: string, }; ================================================ FILE: examples/profile-cards-react-with-styles/src/withStyles.js ================================================ import ThemedStyleSheet from 'react-with-styles/lib/ThemedStyleSheet'; import { css, withStyles, ThemeProvider } from 'react-with-styles'; import { StyleSheet } from 'react-sketchapp'; import theme from './theme'; const Interface = { create(styleHash) { return StyleSheet.create(styleHash); }, resolve(styles) { return { style: styles }; }, }; ThemedStyleSheet.registerDefaultTheme(theme); ThemedStyleSheet.registerInterface(Interface); export { css, withStyles, ThemeProvider, ThemedStyleSheet }; ================================================ FILE: examples/profile-cards-react-with-styles/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/react-router-prototyping/README.md ================================================ # React Router setup ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/react-router-prototyping cd react-router-prototyping ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: React Router prototyping` Run with live reloading in Sketch, need a new sketch doc open ```bash npm run render ``` ## The idea behind the example `react-sketchapp` allows you to build prototypes with navigation. This example shows how you build a Sketch prototype with `react-sketchapp-router`, while being able to share your routing code with your React web or React Native app. ![examples-react-router](https://user-images.githubusercontent.com/6757532/76151291-57fb9100-60ab-11ea-8b07-60916eac0a6e.png) ================================================ FILE: examples/react-router-prototyping/package.json ================================================ { "name": "react-router-prototyping", "version": "1.0.0", "description": "", "skpm": { "main": "react-router-prototyping.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Macintosh Helper ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.4.3" }, "dependencies": { "prop-types": "^15.5.8", "react": "^16.13.0", "react-router-primitives": "^0.1.2", "react-sketchapp": "^3.1.0", "react-sketchapp-router": "^0.1.3", "react-test-renderer": "^16.13.0" } } ================================================ FILE: examples/react-router-prototyping/src/App.js ================================================ import React from 'react'; import { SketchRouter, Switch, Route } from 'react-sketchapp-router'; import Home from './routes/home'; import About from './routes/about'; import Post from './routes/post'; import Profile from './routes/profile'; const App = () => ( {/* (Need to have menus/sidebars inside of a Route) */} } /> } /> } /> } /> {/* } /> */} ); export default App; ================================================ FILE: examples/react-router-prototyping/src/components/AppBar.js ================================================ import React from 'react'; import { View, Text } from 'react-sketchapp'; import { Link } from 'react-router-primitives'; const AppBar = () => ( My Blog ); export default AppBar; ================================================ FILE: examples/react-router-prototyping/src/components/NavBar.js ================================================ import React from 'react'; import { View, Text } from 'react-sketchapp'; import { Link } from 'react-router-primitives'; const MenuItem = ({ name, href }) => ( {name} ); const NavBar = () => ( {[{ name: 'About Us', href: '/about' }].map((props) => ( ))} ); export default NavBar; ================================================ FILE: examples/react-router-prototyping/src/main.js ================================================ /* global context */ import * as React from 'react'; import { render, Page, Document as RootDocument } from 'react-sketchapp'; import App from './App'; const pages = [ { name: 'App', component: App, }, ]; const Document = () => ( {pages.map(({ name, component: Component }) => ( ))} ); export default () => { const data = context.document.documentData(); const pages = context.document.pages(); data.setCurrentPage(pages.firstObject()); render(); }; ================================================ FILE: examples/react-router-prototyping/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: React Router prototyping", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/react-router-prototyping/src/routes/about.js ================================================ import React from 'react'; import { View, Text } from 'react-sketchapp'; import AppBar from '../components/AppBar'; const About = () => ( About Us There is not a lot of information here about us. ); export default About; ================================================ FILE: examples/react-router-prototyping/src/routes/home.js ================================================ import React from 'react'; import { View, Text } from 'react-sketchapp'; import { Link } from 'react-router-primitives'; import AppBar from '../components/AppBar'; import NavBar from '../components/NavBar'; const PostSummary = () => ( Title of a Blog Post Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. {`> `} Click here to read more ); const Home = () => ( ); export default Home; ================================================ FILE: examples/react-router-prototyping/src/routes/post.js ================================================ import React from 'react'; import { View, Text } from 'react-sketchapp'; import { Link } from 'react-router-primitives'; import AppBar from '../components/AppBar'; import NavBar from '../components/NavBar'; const posts = { '1': { title: 'Title of a Blog Post', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', author: { id: 'john', name: 'John', }, }, }; const Post = ({ id }) => { return ( {id && ( {posts[id].title} {posts[id].author.name} {posts[id].content} )} ); }; export default Post; ================================================ FILE: examples/react-router-prototyping/src/routes/profile.js ================================================ import React from 'react'; import { View, Text } from 'react-sketchapp'; import AppBar from '../components/AppBar'; import NavBar from '../components/NavBar'; const Profile = ({ user }) => { const name = user ? `${user.charAt(0).toUpperCase()}${user.slice(1)}` : 'User not found'; return ( {name} ); }; export default Profile; ================================================ FILE: examples/react-router-prototyping/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/styled-components/README.md ================================================ # Styled-components ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/styled-components cd styled-components ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Styled components` ### Run it in Sketch Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example `styled-components` allows you to write actual CSS code to style your components. It also removes the mapping between components and styles ================================================ FILE: examples/styled-components/package.json ================================================ { "name": "styled-components-example", "version": "1.0.0", "description": "", "skpm": { "main": "styled-components.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Mathieu Dutour ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.4.0" }, "dependencies": { "chroma-js": "^1.2.2", "prop-types": "^15.5.8", "react": "^16.3.2", "react-primitives": "^0.6.0", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2", "styled-components": "^2.1.0" } } ================================================ FILE: examples/styled-components/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Styled components", "identifier": "main", "script": "./my-command.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/styled-components/src/my-command.js ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import styled from 'styled-components/primitives'; import { render } from 'react-sketchapp'; import chroma from 'chroma-js'; // take a hex and give us a nice text color to put over it const textColor = (hex) => { const vsWhite = chroma.contrast(hex, 'white'); if (vsWhite > 4) { return '#FFF'; } return chroma(hex).darken(3).hex(); }; const SwatchTile = styled.View` height: 250px; width: 250px; border-radius: 4px; margin: 4px; background-color: ${(props) => props.hex}; justify-content: center; align-items: center; `; const SwatchName = styled.Text` color: ${(props) => textColor(props.hex)}; font-weight: bold; `; const Ampersand = styled.Text` color: #f3f3f3; font-size: 120px; font-family: Himalaya; line-height: 144px; `; const Title = styled.Text` font-size: 24px; font-family: 'GT America'; font-weight: bold; padding: 4px; `; const Swatch = ({ name, hex }) => ( {name} & ); const Color = { hex: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }; Swatch.propTypes = Color; const Artboard = styled.View` flex-direction: row; flex-wrap: wrap; width: ${(96 + 8) * 4}px; justify-content: center; `; const Document = ({ colors }) => ( Max’s Sweaters {Object.keys(colors).map((color) => ( ))} ); Document.propTypes = { colors: PropTypes.objectOf(PropTypes.string).isRequired, }; export default () => { const colorList = { Classic: '#96324E', Neue: '#21304E', }; render(, context.document.currentPage()); }; ================================================ FILE: examples/styled-components/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/styleguide/.flowconfig ================================================ [ignore] [include] [libs] [options] ================================================ FILE: examples/styleguide/README.md ================================================ # Styleguide ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/styleguide cd styleguide ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Styleguide` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example The reason we started `react-sketchapp` was to build dynamic styleguides! This is an example showing how to quickly render rich styleguides from JavaScript design system definition. It uses `chroma-js` to dynamically generate color contrast labels. ![examples-styleguide](https://cloud.githubusercontent.com/assets/591643/24778196/2a4ef41a-1ade-11e7-9805-8d974bbfd708.png) ================================================ FILE: examples/styleguide/package.json ================================================ { "name": "styleguide", "private": true, "skpm": { "main": "styleguide.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "dependencies": { "chroma-js": "^1.2.2", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/styleguide/src/components/AccessibilityBadge.js ================================================ import * as React from 'react'; import Badge from './Badge'; const AccessibilityBadge = ({ level }) => { let text; switch (true) { case level.aaa: text = 'AAA'; break; case level.aa: text = 'AA'; break; case level.aaLarge: text = 'AA Large'; break; default: text = null; } return text && {text}; }; export default AccessibilityBadge; ================================================ FILE: examples/styleguide/src/components/Badge.js ================================================ import * as React from 'react'; import { View, Text } from 'react-sketchapp'; const Badge = ({ children, filled = false }) => ( {children} ); export default Badge; ================================================ FILE: examples/styleguide/src/components/Label.js ================================================ import * as React from 'react'; import { Text } from 'react-sketchapp'; const Label = ({ bold, children }) => ( {children} ); export default Label; ================================================ FILE: examples/styleguide/src/components/Palette.js ================================================ import * as React from 'react'; import { View } from 'react-sketchapp'; import Swatch from './Swatch'; const SWATCH_WIDTH = 100; const Palette = ({ colors }) => ( {Object.keys(colors).map((name) => ( ))} ); export default Palette; ================================================ FILE: examples/styleguide/src/components/Section.js ================================================ import * as React from 'react'; import { View } from 'react-sketchapp'; import Label from './Label'; const Section = ({ title, children }) => ( {children} ); export default Section; ================================================ FILE: examples/styleguide/src/components/Swatch.js ================================================ import * as React from 'react'; import { View } from 'react-sketchapp'; import AccessibilityBadge from './AccessibilityBadge'; import Label from './Label'; const SWATCH_WIDTH = 100; const Swatch = ({ color, name }) => ( ); export default Swatch; ================================================ FILE: examples/styleguide/src/components/TypeSpecimen.js ================================================ import * as React from 'react'; import { View, Text } from 'react-sketchapp'; import Label from './Label'; const TypeSpecimen = ({ name, style }) => ( {name} ); export default TypeSpecimen; ================================================ FILE: examples/styleguide/src/designSystem.js ================================================ import processColor from './processColor'; export const colors = { Haus: '#F3F4F4', Night: '#333', Sur: '#96DBE4', 'Sur a11y': '#24828F', Peach: '#EFADA0', 'Peach a11y': '#E37059', Pear: '#93DAAB', 'Pear a11y': '#2E854B', }; const typeSizes = [80, 48, 36, 24, 20, 16]; export const spacing = 16; const fontFamilies = { display: 'Helvetica', body: 'Georgia', }; const fontWeights = { regular: 'regular', bold: 'bold', }; export const fonts = { Headline: { color: colors.Night, fontSize: typeSizes[0], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 80, }, 'Title 1': { color: colors.Night, fontSize: typeSizes[2], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 48, }, 'Title 2': { color: colors.Night, fontSize: typeSizes[3], fontFamily: fontFamilies.display, fontWeight: fontWeights.bold, lineHeight: 36, }, 'Title 3': { color: colors.Night, fontSize: typeSizes[4], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, }, Body: { color: colors.Night, fontSize: typeSizes[5], fontFamily: fontFamilies.body, fontWeight: fontWeights.regular, lineHeight: 24, marginBottom: 24, }, }; export default { colors: Object.keys(colors).reduce( (acc, name) => ({ ...acc, [name]: processColor(colors[name]), }), {}, ), fonts, spacing, }; ================================================ FILE: examples/styleguide/src/main.js ================================================ import * as React from 'react'; import { render, TextStyles, View } from 'react-sketchapp'; import designSystem from './designSystem'; import Label from './components/Label'; import Palette from './components/Palette'; import Section from './components/Section'; import TypeSpecimen from './components/TypeSpecimen'; const Document = ({ system }) => (
{Object.keys(system.fonts).map((name) => ( ))}
); export default () => { TextStyles.create(designSystem.fonts, { clearExistingStyles: true, }); render(, context.document.currentPage()); }; ================================================ FILE: examples/styleguide/src/manifest.json ================================================ { "compatibleVersion": 1, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Styleguide", "identifier": "main", "script": "./main.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/styleguide/src/processColor.js ================================================ import chroma from 'chroma-js'; const minimums = { aa: 4.5, aaLarge: 3, aaa: 7, aaaLarge: 4.5, }; export default (hex) => { const contrast = chroma.contrast(hex, 'white'); return { hex, contrast, accessibility: { aa: contrast >= minimums.aa, aaLarge: contrast >= minimums.aaLarge, aaa: contrast >= minimums.aaa, aaaLarge: contrast >= minimums.aaaLarge, }, }; }; ================================================ FILE: examples/styleguide/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/symbols/README.md ================================================ # Symbol Support ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/symbols cd symbols ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Symbol Support` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example `react-sketchapp@^0.11.0` introduces an API for creating Sketch symbols — this example shows them in use with React components. ================================================ FILE: examples/symbols/package.json ================================================ { "name": "symbols", "version": "1.0.0", "description": "", "skpm": { "main": "symbols.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", "devDependencies": { "@skpm/builder": "^0.4.0" }, "dependencies": { "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" } } ================================================ FILE: examples/symbols/src/manifest.json ================================================ { "compatibleVersion": 3, "bundleVersion": 1, "commands": [ { "name": "react-sketchapp: Symbol Support", "identifier": "main", "script": "./my-command.js" } ], "menu": { "isRoot": true, "items": [ "main" ] } } ================================================ FILE: examples/symbols/src/my-command.js ================================================ import * as React from 'react'; import { render, Artboard, Text, View, Image, makeSymbol } from 'react-sketchapp'; const RedSquare = () => ( Red Square ); const RedSquareSym = makeSymbol(RedSquare, 'squares/red'); const BlueSquare = () => ( Blue Square ); const BlueSquareSym = makeSymbol(BlueSquare, 'squares/blue'); const Photo = () => ( ); const PhotoSym = makeSymbol(Photo); const Nested = () => ( ); const NestedSym = makeSymbol(Nested); export default () => { const Document = () => ( ); render(, context.document.currentPage()); }; ================================================ FILE: examples/symbols/webpack.skpm.config.js ================================================ const path = require('path'); module.exports = (config) => { if (process.env.LOCAL_DEV) { config.resolve = { ...config.resolve, alias: { ...config.resolve.alias, 'react-sketchapp': path.resolve(__dirname, '../../'), }, }; } }; ================================================ FILE: examples/timeline-airtable/.eslintrc ================================================ { "env": { "browser": true } } ================================================ FILE: examples/timeline-airtable/README.md ================================================ # Timeline w/ Airtable ## How to use Download the example or [clone the repo](http://github.com/airbnb/react-sketchapp): ```bash curl https://codeload.github.com/airbnb/react-sketchapp/tar.gz/master | tar -xz --strip=2 react-sketchapp-master/examples/timeline-airtable cd timeline-airtable ``` Install the dependencies ```bash npm install ``` Then, open Sketch and navigate to `Plugins → react-sketchapp: Timeline w/ Airtable` Run with live reloading in Sketch ```bash npm run render ``` ## The idea behind the example Simple timeline feed demonstrates how to retrieve records from Airtable to design with real data ![timeline-airtable](https://cloud.githubusercontent.com/assets/21080/25830456/cdc67bf8-3411-11e7-8998-fdef507ab0d2.png) ================================================ FILE: examples/timeline-airtable/package.json ================================================ { "name": "timeline-airtable", "private": true, "skpm": { "main": "timeline-airtable.sketchplugin", "manifest": "src/manifest.json" }, "scripts": { "build": "skpm-build", "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", "postinstall": "npm run build && skpm-link" }, "author": "David E. Chen ", "license": "MIT", "dependencies": { "prop-types": "^15.5.8", "react": "^16.3.2", "react-sketchapp": "^3.0.0", "react-test-renderer": "^16.3.2" }, "devDependencies": { "@skpm/builder": "^0.4.0" } } ================================================ FILE: examples/timeline-airtable/src/main.js ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { render, Artboard, Text, View, StyleSheet } from 'react-sketchapp'; const API_ENDPOINT_URL = 'https://api.airtable.com/v0/appFs7J3WdgHYCDxD/Features?api_key=keyu4dudakWLI0vAh&&sort%5B0%5D%5Bfield%5D=Target+Launch+Date&sort%5B0%5D%5Bdirection%5D=asc'; const styles = StyleSheet.create({ artboard: { backgroundColor: '#F9FDFF', }, verticalLine: { width: 3, }, dot: { width: 24, height: 24, borderRadius: 12, borderWidth: 2, borderColor: '#46D2B3', }, dotCompleted: { backgroundColor: '#46D2B3', }, title: { fontSize: 48, fontWeight: 200, color: '#000', }, rowContainer: { width: 800, flexDirection: 'row', flex: 1, paddingLeft: 30, paddingRight: 30, }, rowDescription: { fontSize: 16, width: 400, }, rowLeftArea: { width: 99, // odd number to avoid antialiasing alignItems: 'center', height: 150, }, rowDate: { fontSize: 10, color: '#46D2B3', }, rowTitle: { fontSize: 20, }, }); const VerticalLine = ({ height = 1, color = '#46D2B3' }) => ( ); VerticalLine.propTypes = { height: PropTypes.number, color: PropTypes.string, }; const Header = ({ title }) => ( {title} ); Header.propTypes = { title: PropTypes.string, }; const Footer = () => ( ); const Dot = ({ completed }) => ( ); Dot.propTypes = { completed: PropTypes.bool, }; const Row = ({ title, description, completed, date, status }) => ( {`${status} on ${date}`} {title} {description} ); Row.propTypes = { title: PropTypes.string, description: PropTypes.string, completed: PropTypes.bool, date: PropTypes.string, status: PropTypes.string, }; const Timeline = (props) => (
{props.data.records.map(({ id, fields }) => ( ))}