├── .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 | 
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 |
62 |
--------------------------------------------------------------------------------
/svg/contributors.svg:
--------------------------------------------------------------------------------
1 |
173 |
--------------------------------------------------------------------------------