├── .gitignore ├── .storybook ├── addons.js ├── config.js └── webpack.config.js ├── stories ├── emails.stories.js ├── issues.stories.js ├── minified.stories.js ├── resolution.stories.js ├── contributors.stories.js ├── user-feedback.stories.js └── suggested-assignees.stories.js ├── .eslintrc.json ├── .stylelintrc ├── styles ├── user-feedback.css ├── contributors.css ├── issues.css ├── emails.css ├── suggested-assignees.css └── resolution.css ├── package.json ├── gulpfile.js ├── get.js ├── README.md ├── dist ├── resolution.svg └── contributors.svg └── svg ├── resolution.svg └── contributors.svg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | -------------------------------------------------------------------------------- /stories/emails.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import emails from '../dist/emails.svg'; 5 | 6 | storiesOf('Emails').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /stories/issues.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import issues from '../dist/issues.svg'; 5 | 6 | storiesOf('Issues').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /stories/minified.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import minified from '../dist/minified.svg'; 5 | 6 | storiesOf('Minified').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /stories/resolution.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import resolution from '../dist/resolution.svg'; 5 | 6 | storiesOf('Resolution').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /stories/contributors.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import contributors from '../dist/contributors.svg'; 5 | 6 | storiesOf('Contributors').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /stories/user-feedback.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import userFeedback from '../dist/user-feedback.svg'; 5 | 6 | storiesOf('User Feedback').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:prettier/recommended" 10 | ], 11 | "parserOptions": { 12 | "sourceType": "module", 13 | "ecmaVersion": 2018 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /stories/suggested-assignees.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {storiesOf} from '@storybook/react'; 3 | 4 | import suggestedAssignees from '../dist/suggested-assignees.svg'; 5 | 6 | storiesOf('Suggested Assignees').add('default', () => ( 7 | 8 | )); 9 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | // automatically import all files ending in *.stories.js 4 | const req = require.context('../stories', true, /\.stories\.js$/); 5 | function loadStories() { 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-styled-components"], 3 | "extends": [ 4 | "stylelint-config-recommended", 5 | "stylelint-config-styled-components" 6 | ], 7 | "rules": { 8 | "declaration-colon-newline-after": null, 9 | "block-no-empty": null, 10 | "selector-type-no-unknown": [true, { 11 | "ignoreTypes": ["$dummyValue"], 12 | }], 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /styles/user-feedback.css: -------------------------------------------------------------------------------- 1 | @keyframes animate-top-left-in { 0% {transform: translate(-5%, 3%);} 100% {transform: translate(0%, 0%);}} 2 | @keyframes animate-bottom-right-in { 0% {transform: translate(5%, 3%);} 100% {transform: translate(0%, 0%);}} 3 | 4 | #chat-box { 5 | animation: 0.5s animate-top-left-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 6 | transform-origin: center center; 7 | } 8 | 9 | #chat-avatar { 10 | animation: 0.5s animate-bottom-right-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 11 | transform-origin: center center; 12 | } -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path').normalize; 2 | 3 | module.exports = { 4 | entry: './index.js', 5 | output: { 6 | path: path(__dirname + '/dist'), 7 | filename: 'index.js' 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test:/\.css$/, 13 | use:['style-loader','css-loader'] 14 | }, 15 | { 16 | test: /\.(png|jpg|gif|svg)$/, 17 | use: [ 18 | { 19 | loader: 'file-loader', 20 | options: {}, 21 | }, 22 | ], 23 | } 24 | ] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /styles/contributors.css: -------------------------------------------------------------------------------- 1 | @keyframes animate-top-left-in { 2 | 0% { 3 | transform: translate(-5%, -5%); 4 | } 5 | 6 | 100% { 7 | transform: translate(0%, 0%); 8 | } 9 | } 10 | 11 | @keyframes animate-bottom-right-in { 12 | 0% { 13 | transform: translate(5%, 5%); 14 | } 15 | 16 | 100% { 17 | transform: translate(0%, 0%); 18 | } 19 | } 20 | 21 | #card-top { 22 | animation: 0.5s animate-top-left-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 23 | transform-origin: center center; 24 | } 25 | 26 | #card-bottom { 27 | animation: 0.5s animate-bottom-right-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 28 | transform-origin: center center; 29 | } 30 | -------------------------------------------------------------------------------- /styles/issues.css: -------------------------------------------------------------------------------- 1 | @keyframes animate-top-left-in { 2 | 0% { 3 | transform: translate(-5%, -5%); 4 | } 5 | 6 | 100% { 7 | transform: translate(0%, 0%); 8 | } 9 | } 10 | 11 | @keyframes animate-bottom-right-in { 12 | 0% { 13 | transform: translate(5%, 5%); 14 | } 15 | 16 | 100% { 17 | transform: translate(0%, 0%); 18 | } 19 | } 20 | 21 | #resolved-issues { 22 | animation: 0.5s animate-top-left-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 23 | transform-origin: center center; 24 | } 25 | 26 | #file-changes { 27 | animation: 0.5s animate-bottom-right-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 28 | transform-origin: center center; 29 | } 30 | -------------------------------------------------------------------------------- /styles/emails.css: -------------------------------------------------------------------------------- 1 | @keyframes animate-top-left-in { 2 | 0% { 3 | transform: translate(-5%, -5%); 4 | } 5 | 6 | 100% { 7 | transform: translate(0%, 0%); 8 | } 9 | } 10 | 11 | @keyframes animate-bottom-right-in { 12 | 0% { 13 | transform: translate(5%, 5%); 14 | } 15 | 16 | 100% { 17 | transform: translate(0%, 0%); 18 | } 19 | } 20 | 21 | 22 | #email-card-bottom { 23 | animation: 0.5s animate-top-left-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 24 | transform-origin: center center; 25 | } 26 | 27 | #email-card-top { 28 | animation: 0.5s animate-bottom-right-in cubic-bezier(0.68, -0.55, 0.265, 1.55); 29 | transform-origin: center center; 30 | } 31 | -------------------------------------------------------------------------------- /styles/suggested-assignees.css: -------------------------------------------------------------------------------- 1 | @keyframes spin { 2 | 0% { 3 | transform: rotate(0deg); 4 | } 5 | 6 | 8% { 7 | transform: rotate(60deg); 8 | } 9 | 10 | 25% { 11 | transform: rotate(60deg); 12 | } 13 | 14 | 33% { 15 | transform: rotate(240deg); 16 | } 17 | 18 | 60% { 19 | transform: rotate(240deg); 20 | } 21 | 22 | 68% { 23 | transform: rotate(180deg); 24 | } 25 | 26 | 96% { 27 | transform: rotate(180deg); 28 | } 29 | 30 | 100% { 31 | transform: rotate(0deg); 32 | } 33 | } 34 | 35 | #assignee-dial { 36 | animation: 10s spin cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; 37 | transform-origin: center center; 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sentry-dreamy-components", 3 | "version": "2.0.1", 4 | "description": "Totally Sweet Looking Components That Do Absolutely Nothing", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "getsentry/dreamy-components" 9 | }, 10 | "keywords": [ 11 | "web", 12 | "illustrations", 13 | "components" 14 | ], 15 | "author": "Chrissy ", 16 | "license": "SIL OFL 1.1", 17 | "bugs": { 18 | "url": "https://github.com/getsentry/dreamy-components/issues" 19 | }, 20 | "homepage": "https://github.com/getsentry/dreamy-components", 21 | "scripts": { 22 | "start": "start-storybook -p 6006", 23 | "storybook": "start-storybook -p 6006", 24 | "get": "node get.js", 25 | "watch": "npx gulp watch", 26 | "build-storybook": "build-storybook" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.4.5", 30 | "@storybook/addon-actions": "^5.1.9", 31 | "@storybook/addon-links": "^5.1.9", 32 | "@storybook/addons": "^5.1.9", 33 | "@storybook/react": "^5.1.9", 34 | "babel-loader": "^8.0.6", 35 | "fs-extra": "^8.0.1", 36 | "glob": "^7.1.4", 37 | "gulp": "^4.0.2", 38 | "request": "^2.88.0", 39 | "svgo": "^1.2.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /styles/resolution.css: -------------------------------------------------------------------------------- 1 | @keyframes resolve { 2 | 0% { 3 | transform: scale(1); 4 | } 5 | 6 | 50% { 7 | transform: scale(1.05); 8 | } 9 | 10 | 100% { 11 | transform: scale(1); 12 | } 13 | } 14 | 15 | @keyframes fade-in { 16 | 0% { 17 | opacity: 0; 18 | } 19 | 20 | 1% { 21 | opacity: 1; 22 | } 23 | 24 | 99% { 25 | opacity: 1; 26 | } 27 | 28 | 100% { 29 | opacity: 0; 30 | } 31 | } 32 | 33 | 34 | @keyframes fade-out { 35 | 0% { 36 | opacity: 1; 37 | } 38 | 39 | 1% { 40 | opacity: 0; 41 | } 42 | 43 | 99% { 44 | opacity: 0; 45 | } 46 | 47 | 100% { 48 | opacity: 1; 49 | } 50 | } 51 | 52 | #release-1, #release-2, #release-3, #release-4 { 53 | animation: 0.3s resolve ease-out; 54 | transform-origin: center center; 55 | } 56 | 57 | #checkmark-1, #checkmark-2, #checkmark-3, #checkmark-4 { 58 | animation: 5s fade-in ease-out; 59 | } 60 | 61 | #exclamation-1, #exclamation-2, #exclamation-3, #exclamation-4 { 62 | animation: 5s fade-out ease-out; 63 | } 64 | 65 | #release-2, #checkmark-2, #exclamation-2, #rectangle-2 { 66 | animation-delay: 0.25s; 67 | } 68 | 69 | #release-3, #checkmark-3, #exclamation-3, #rectangle-3 { 70 | animation-delay: 0.5s; 71 | } 72 | 73 | #release-4, #checkmark-4, #exclamation-4, #rectangle-4 { 74 | animation-delay: 0.75s; 75 | } 76 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"); 2 | const glob = require("glob"); 3 | const Svgo = require("svgo"); 4 | const fs = require("fs"); 5 | 6 | /* 7 | todo: this should be more in the 'gulp' way, 8 | instead of just using it to watch files and 9 | run node stuff 10 | */ 11 | 12 | const svgoOpts = { 13 | plugins: [ 14 | { 15 | inlineStyles: false 16 | }, { 17 | convertStyleToAttrs: false 18 | }, { 19 | removeViewBox: false 20 | }, { 21 | removeDimensions: false 22 | } 23 | ] 24 | }; 25 | 26 | const getStyles = (name) => new Promise(resolve => { 27 | const path = `./styles/${name}.css`; 28 | 29 | if (!fs.existsSync(path)) resolve(null); 30 | fs.readFile(path, "utf8", (err, data) => { 31 | resolve(data); 32 | }) 33 | }); 34 | 35 | const getSvg = (name) => new Promise(resolve => { 36 | const path = `./svg/${name}.svg`; 37 | 38 | if (!fs.existsSync(path)) resolve(null); 39 | fs.readFile(path, "utf8", (err, data) => { 40 | resolve(data); 41 | }) 42 | }); 43 | 44 | const inlineStyles = (done) => { 45 | let allStyles = ""; 46 | 47 | glob("svg/*.svg", (err, files) => { 48 | files.map(filePath => { 49 | const fileName = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.')); 50 | Promise.all([getStyles(fileName), getSvg(fileName)]).then(([styles, svg]) => { 51 | const concat = styles ? svg.replace(/\<\/svg\>/, ``) : svg; 52 | new Svgo(svgoOpts).optimize(concat).then(result => { 53 | fs.writeFileSync(`./dist/${fileName}.svg`, result.data) 54 | }) 55 | }); 56 | }); 57 | }); 58 | 59 | return done(); 60 | }; 61 | 62 | gulp.task("watch", () => gulp.watch("./styles/*.css", inlineStyles)); 63 | gulp.task("inlineStyles", inlineStyles); 64 | -------------------------------------------------------------------------------- /get.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const request = require('request'); 3 | const fsExtra = require('fs-extra'); 4 | const { spawn } = require('child_process'); 5 | 6 | //remove this for oAuth before merging into master 7 | const headers = { 8 | 'X-Figma-Token': '15587-d15f8f57-cd23-4df2-940b-bedeb9cc1263' 9 | }; 10 | 11 | const writeDir = './svg'; 12 | 13 | const getDocument = () => new Promise(resolve => { 14 | request.get( 15 | { 16 | url: 'https://api.figma.com/v1/files/DoS7E7LzdboGbdMAN9tDu1Xf', 17 | headers 18 | } 19 | , function(error, response, body) { 20 | resolve(JSON.parse(body)); 21 | }) 22 | }); 23 | 24 | const getSvgUrls = ids => new Promise(resolve => { 25 | request.get( 26 | { 27 | url: 'https://api.figma.com/v1/images/DoS7E7LzdboGbdMAN9tDu1Xf', 28 | headers, 29 | qs: { 30 | //ids expect a comma-seperated string 31 | ids: ids.join(","), 32 | format: 'svg', 33 | svg_include_id: true 34 | } 35 | } 36 | , function(error, response, body) { 37 | const {images} = JSON.parse(body); 38 | //image urls come back as a key:value hash 39 | const urls = Object.keys(images).map(key => { 40 | return images[key]; 41 | }); 42 | resolve(urls) 43 | }) 44 | }); 45 | 46 | const getSvg = url => new Promise(resolve => { 47 | request.get({url}, function(error, response, body) { 48 | resolve(body); 49 | }); 50 | }); 51 | 52 | getDocument().then(response => { 53 | const artboards = response 54 | .document 55 | .children 56 | .find(child => child.name == 'Exports') 57 | .children; 58 | 59 | const artboardIds = artboards 60 | .map(({id}) => id); 61 | 62 | getSvgUrls(artboardIds).then(urls => { 63 | Promise.all( 64 | urls.map(url => getSvg(url)) 65 | ).then(svgs => { 66 | 67 | //delete everything in the write directory 68 | fsExtra.emptyDirSync(writeDir) 69 | 70 | svgs.map((svg, index) => { 71 | fs.writeFileSync( 72 | `${writeDir}/${artboards[index].name}.svg`, 73 | svg 74 | ); 75 | spawn('gulp', ['inlineStyles']); 76 | }) 77 | }) 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://i.imgur.com/NIggrRK.gif) 2 | 3 | ### Dreamy Components 4 | 5 | Sentry Dreamy Components are a series of compiled animated SVGs. They are framework agnostic and can be imported directly from a node module using your favorite loader, similarly to `@material-ui/icons`; 6 | 7 | 8 | ### Usage 9 | 10 | For performance reasons, I recommend using the `object` tag and an inline loader like webpack's `file-loader`: 11 | 12 | ``` 13 | import React from 'react'; 14 | import emails from 'sentry-dreamy-components/dist/emails.svg'; 15 | 16 | return ; 17 | ``` 18 | 19 | ### Making design changes 20 | 21 | As of Dreamy Components V2, this project will make heavy use of Figma. To edit copy, colors, avatars, etc., check out 22 | [this open file](https://www.figma.com/file/DoS7E7LzdboGbdMAN9tDu1Xf/dreamy-ui). 23 | 24 | Anything in the `exports` page will be turned into an svg and bundled in with the package the next time a package 25 | developer updates the package. 26 | 27 | For quality assurance, you will need to run the application in development and check your changes in storybook before merging into the repo. The same goes for keyframe animations. I will occasionally rebuild the components from Figma, but if you want a copy change, the easiest way is to rebuild yourself as shown below. 28 | 29 | ### Committing design changes and keyframe animation changes 30 | 31 | First, grab this repo and download development dependencies. You will need a relatively new version of Node and a package manager like NPM or Yarn: 32 | 33 | ``` 34 | git clone git@github.com:getsentry/dreamy-components.git 35 | cd dreamy-components 36 | yarn 37 | ``` 38 | 39 | To pull from Figma: 40 | 41 | ``` 42 | yarn run get 43 | ``` 44 | 45 | To check changes in storybook: 46 | 47 | ``` 48 | yarn start 49 | 50 | ``` 51 | 52 | To watch for css changes in development: 53 | 54 | ``` 55 | yarn run watch 56 | ``` 57 | 58 | ### Adding keyframe animations 59 | 60 | As of V2 this package makes usage of Figma's ability to export SVGs with their layer name as an ID. Keyframe animations can be written in a css file of the same name, and attached to these IDs: 61 | 62 | ``` 63 | #release-1, #release-2, #release-3, #release-4 { 64 | animation: 0.3s resolve ease-out; 65 | transform-origin: center center; 66 | } 67 | ``` 68 | 69 | SVGO will automatically inline all styles. That, combined with the recommended use of the `object` tag means you will not need to worry about css namespace issues. 70 | -------------------------------------------------------------------------------- /dist/resolution.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/contributors.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/resolution.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /svg/contributors.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | --------------------------------------------------------------------------------