├── .gitignore ├── .prettierignore ├── .storybook ├── addon-order.js ├── addons.js ├── preview.js └── webpack.config.js ├── LICENSE ├── README.md ├── doc ├── concept.png ├── example1.png ├── example2.png ├── example3.png ├── innFactory.svg ├── react-planet_bring_it_to_life.gif ├── react-planet_menu.gif ├── react-planet_physic.gif ├── react-planet_planetception.gif ├── react-planet_satelliteOrientation.gif └── react-planet_space.gif ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── components │ ├── DragableContainer.tsx │ ├── Orbit.tsx │ ├── Planet.tsx │ ├── Satellite.tsx │ └── index.ts ├── index.ts ├── stories │ ├── Configurator.stories.tsx │ ├── CustomOrbit.stories.tsx │ ├── Half.stories.tsx │ ├── Menu.stories.tsx │ ├── Planetception.stories.tsx │ ├── WeirdPlanet.stories.tsx │ ├── WeirdSatellites.stories.tsx │ ├── index.stories.tsx │ └── storybook_utils.tsx │ │ ├── generateSatellites.tsx │ │ └── utils.ts └── typings.d.ts ├── tsconfig.json └── tsconfig.test.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ -------------------------------------------------------------------------------- /.storybook/addon-order.js: -------------------------------------------------------------------------------- 1 | // export action addon this way, so that knob tab is the first one 2 | export * from '@storybook/addon-actions/register'; 3 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register'; 2 | import '@storybook/addon-links/register'; 3 | import '@storybook/addon-viewport/register'; 4 | import './addon-order'; 5 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { withKnobs } from '@storybook/addon-knobs/dist'; 2 | import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; 3 | import { addDecorator, addParameters, configure } from '@storybook/react'; 4 | 5 | addParameters({ 6 | viewport: { 7 | viewports: INITIAL_VIEWPORTS, 8 | }, 9 | }); 10 | 11 | addDecorator(withKnobs); 12 | 13 | configure(require.context('../src', true, /\.stories\.tsx$/), module); 14 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const SRC_PATH = path.join(__dirname, '../src'); 3 | 4 | module.exports = ({ config }) => { 5 | config.module.rules.push({ 6 | test: /\.(ts|tsx)$/, 7 | include: [SRC_PATH], 8 | use: [ 9 | { 10 | loader: require.resolve('awesome-typescript-loader'), 11 | options: { 12 | configFileName: './tsconfig.json', 13 | }, 14 | }, 15 | ], 16 | }); 17 | config.resolve.extensions.push('.ts', '.tsx'); 18 | return config; 19 | }; 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 innFactory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-planet 2 | 3 | [![Version](https://img.shields.io/npm/v/react-planet.svg)](https://www.npmjs.com/package/react-planet) 4 | [![Downloads](https://img.shields.io/npm/dt/react-planet.svg)](https://www.npmjs.com/package/react-planet) 5 |
6 |
7 | A react lib for building circular menus in a very easy and handy way. 8 | 9 | 10 | 11 | Live-Demo: [STORYBOOK](https://innfactory.github.io/react-planet) 12 | 13 | Read the full story @ [Medium](https://medium.com/@innFactory/creating-circular-menus-with-react-planet-8c7c9df6d766) or [innFactory-Blog](https://innfactory.de/softwareentwicklung/ui-ux/creating-circular-menus-with-react-planet/) 14 | 15 | ## install 16 | 17 | ``` 18 | npm install --save react-planet 19 | ``` 20 | 21 | ## Concept 22 | 23 | 24 | 25 | ## Basic Example 26 | 27 | 28 | 29 | ```jsx 30 | import { Planet } from 'react-planet'; 31 | 32 | export function MyPlanet() { 33 | return ( 34 | 44 | } 45 | open 46 | autoClose 47 | > 48 |
56 |
64 | 65 | ); 66 | } 67 | ``` 68 | 69 | ## Change the orbit 70 | 71 | 72 | 73 | ```jsx 74 | } 75 | open 76 | orbitRadius={120} 77 | rotation={30} 78 | ... 79 | > 80 | ``` 81 | 82 | ## Orbit Style 83 | 84 | 85 | 86 | ```jsx 87 | ({ 89 | ...defaultStyle, 90 | borderWidth: 4, 91 | borderStyle: 'dashed', 92 | borderColor: '#6f03fc', 93 | })} 94 | centerContent={
} 95 | open 96 | > 97 |
98 |
99 |
100 | 101 | ``` 102 | 103 | ## Weird satellites and their orientation 104 | 105 | 106 | 107 | ```jsx 108 | 117 | 118 | ```jsx 119 | 130 | 131 | Code: [/src/stories/Planetception.stories.tsx](/src/stories/Planetception.stories.tsx) 132 | 133 | ## Fake the space 134 | 135 | 136 | 137 | ```jsx 138 | } 140 | hideOrbit 141 | autoClose 142 | orbitRadius={60} 143 | bounceOnClose 144 | rotation={105} 145 | // the bounce direction is minimal visible 146 | // but on close it seems the button wobbling a bit to the bottom 147 | bounceDirection="BOTTOM" 148 | > 149 | 150 | 151 | 152 |
153 |
154 |
155 |
156 | 157 | ``` 158 | 159 | ## Alter the physics 160 | 161 | 162 | 163 | ```jsx 164 | 172 | 173 | # Props 174 | 175 | | name | type | example /default | description | 176 | | -------------------- | --------------------------------------- | ------------------------ | -------------------------------------------------------------- | 177 | | centerContent | React.Node? |
| The planet component | 178 | | orbitRadius | number? | 120 | How far the satellites are away from the planet | 179 | | open | boolean? | false | Set the open/close state from outside | 180 | | autoClose | boolean? | false | If true the planet handles the open/close state by itself | 181 | | hideOrbit | boolean? | false | If true the orbit is hidden / not rendered | 182 | | rotation | number? | 0 | The angle for the rotation of all satellites around the planet | 183 | | satelliteOrientation | DEFAULT INSIDE OUTSIDE READABLE | undefined / DEFAULT | The angle for the rotation of one satellite itself | 184 | | dragableSatellites | boolean? | false | If true you can click and drag a satellite | 185 | | dragRadiusSatellites | number? | 12 | Defines how much you can drag the satellites | 186 | | dragablePlanet | boolean? | false | If true you can click and drag the planet | 187 | | dragRadiusPlanet | number? | 12 | Defines how much you can drag the planet | 188 | | bounce | boolean? | false | If true the planet bounces on open and close | 189 | | bounceOnOpen | boolean? | false | If true the planet bounces only on open | 190 | | bounceOnClose | boolean? | false | If true the planet bounces only on close | 191 | | bounceRadius | number? | 3 | Defines how much the planet bounces | 192 | | bounceDirection | TOP BOTTOM LEFT RIGHT | undefined | On hight bounceRadius you can see a direction | 193 | | tension | number? | 500 | a react-spring animation physic value | 194 | | friction | number? | 17 | a react-spring animation physic value | 195 | | mass | number? | 1 | a react-spring animation physic value | 196 | | orbitStyle | (default: CSSProperties)=>CSSProperties | () => ({borderWidth: 4}) | You can override or set a new style for the orbit | 197 | | onClick | (e: MouseEvent)=>void | ()=>{} | The function is triggered if you click on the centerComponent | 198 | | onClose | (e: MouseEvent)=>void | ()=>{} | The function is triggered if the planet wants to close | 199 | 200 | # Start Storybook local 201 | 202 | ``` 203 | npm install 204 | npm start 205 | ``` 206 | 207 |
208 | 209 | # Made by: 210 | 211 |
212 | 213 | 214 | [https://innFactory.de/](https://innFactory.de/) 215 | -------------------------------------------------------------------------------- /doc/concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/concept.png -------------------------------------------------------------------------------- /doc/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/example1.png -------------------------------------------------------------------------------- /doc/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/example2.png -------------------------------------------------------------------------------- /doc/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/example3.png -------------------------------------------------------------------------------- /doc/innFactory.svg: -------------------------------------------------------------------------------- 1 | innFactory -------------------------------------------------------------------------------- /doc/react-planet_bring_it_to_life.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/react-planet_bring_it_to_life.gif -------------------------------------------------------------------------------- /doc/react-planet_menu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/react-planet_menu.gif -------------------------------------------------------------------------------- /doc/react-planet_physic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/react-planet_physic.gif -------------------------------------------------------------------------------- /doc/react-planet_planetception.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/react-planet_planetception.gif -------------------------------------------------------------------------------- /doc/react-planet_satelliteOrientation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/react-planet_satelliteOrientation.gif -------------------------------------------------------------------------------- /doc/react-planet_space.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/react-planet/29fde3c9828eec46562facad63195d1a7ab11974/doc/react-planet_space.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-planet", 3 | "version": "1.0.1", 4 | "description": "A react lib for building circular menus in a very easy and handy way.", 5 | "author": "innFactory", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/innFactory/react-planet" 10 | }, 11 | "keywords": [ 12 | "react-planet", 13 | "planet", 14 | "planet-menu", 15 | "circular", 16 | "round", 17 | "circular-menu", 18 | "menu", 19 | "submenu" 20 | ], 21 | "main": "dist/index.js", 22 | "module": "dist/index.es.js", 23 | "jsnext:main": "dist/index.es.js", 24 | "engines": { 25 | "node": ">=8", 26 | "npm": ">=5" 27 | }, 28 | "scripts": { 29 | "test": "cross-env CI=1 react-scripts-ts test --env=jsdom", 30 | "test:watch": "react-scripts-ts test --env=jsdom", 31 | "build": "rollup -c", 32 | "start": "start-storybook -p 9001 -c .storybook -s ./src/stories/public", 33 | "prepare": "npm run build", 34 | "predeploy": "npm install && npm run build", 35 | "deploy": "storybook-to-ghpages", 36 | "storybook": "npm start", 37 | "build-storybook": "build-storybook" 38 | }, 39 | "dependencies": { 40 | "@material-ui/core": "4.9.12", 41 | "@material-ui/styles": "^4.9.10", 42 | "react-spring": "^8.0.27", 43 | "react-use-gesture": "^7.0.15", 44 | "use-resize-observer": "^6.1.0" 45 | }, 46 | "peerDependencies": { 47 | "react": ">=16.12.0", 48 | "react-dom": ">=16.12.0" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "^7.7.7", 52 | "@material-ui/icons": "4.9.1", 53 | "@storybook/addon-actions": "^5.3.9", 54 | "@storybook/addon-knobs": "^5.3.17", 55 | "@storybook/addon-links": "^5.3.9", 56 | "@storybook/addon-viewport": "^5.3.17", 57 | "@storybook/react": "^5.2.8", 58 | "@storybook/storybook-deployer": "^2.8.5", 59 | "@testing-library/jest-dom": "^4.2.4", 60 | "@testing-library/react": "^9.4.0", 61 | "@types/classnames": "2.2.7", 62 | "@types/jest": "^24.0.24", 63 | "@types/react": "16.9.19", 64 | "@types/react-dom": "16.9.5", 65 | "@types/react-motion": "0.0.29", 66 | "@types/react-virtualized": "9.18.11", 67 | "awesome-typescript-loader": "^5.2.1", 68 | "babel-loader": "^8.0.6", 69 | "jest": "^24.9.0", 70 | "prettier": "2.0.2", 71 | "react": "16.12.0", 72 | "react-dom": "16.12.0", 73 | "react-easy-crop": "2.1.1", 74 | "rollup": "^1.27.13", 75 | "rollup-plugin-commonjs": "^10.1.0", 76 | "rollup-plugin-node-resolve": "^5.2.0", 77 | "rollup-plugin-peer-deps-external": "^2.2.0", 78 | "rollup-plugin-typescript2": "^0.25.3", 79 | "ts-jest": "^24.2.0", 80 | "typescript": "3.7.5" 81 | }, 82 | "files": [ 83 | "dist" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import external from 'rollup-plugin-peer-deps-external'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import pkg from './package.json'; 6 | 7 | export default { 8 | input: 'src/index.ts', 9 | output: [ 10 | { 11 | file: pkg.main, 12 | format: 'cjs', 13 | exports: 'named', 14 | sourcemap: true, 15 | }, 16 | { 17 | file: pkg.module, 18 | format: 'es', 19 | exports: 'named', 20 | sourcemap: true, 21 | }, 22 | ], 23 | plugins: [ 24 | external(), 25 | resolve(), 26 | typescript({ 27 | rollupCommonJSResolveHack: true, 28 | exclude: ['**/__tests__/**', '**/*.stories.tsx'], 29 | clean: true, 30 | }), 31 | commonjs({ 32 | include: [/node_modules/], 33 | namedExports: { 34 | 'node_modules/react/react.js': ['Children', 'Component', 'PropTypes', 'createElement'], 35 | 'node_modules/react-dom/index.js': ['render'], 36 | 'prop-types': [ 37 | 'array', 38 | 'bool', 39 | 'func', 40 | 'number', 41 | 'object', 42 | 'string', 43 | 'symbol', 44 | 'any', 45 | 'arrayOf', 46 | 'element', 47 | 'elementType', 48 | 'instanceOf', 49 | 'node', 50 | 'objectOf', 51 | 'oneOf', 52 | 'oneOfType', 53 | 'shape', 54 | 'exact', 55 | ], 56 | 'node_modules/react-is/index.js': [ 57 | 'isElement', 58 | 'isValidElementType', 59 | 'ForwardRef', 60 | 'Memo', 61 | 'isFragment', 62 | ], 63 | }, 64 | }), 65 | ], 66 | }; 67 | -------------------------------------------------------------------------------- /src/components/DragableContainer.tsx: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | import { makeStyles } from '@material-ui/core'; 3 | import * as React from "react"; 4 | import { animated, useSpring } from "react-spring"; 5 | import { useDrag } from "react-use-gesture"; 6 | interface Props { 7 | children: React.ReactNode; 8 | on: boolean; 9 | dragable?: boolean; 10 | dragRadius?: number; 11 | bounceRadius?: number; 12 | open?: boolean; 13 | bounceOnOpen?: boolean; 14 | bounceOnClose?: boolean; 15 | bounceDirection?: "TOP" | "BOTTOM" | "LEFT" | "RIGHT"; 16 | } 17 | 18 | const DEFAULT_DRAG_RADIUS = 12; 19 | const DEFAULT_BOUNCE_RADIUS = 3; 20 | 21 | export function DragableContainer(props: Props) { 22 | const { 23 | children, 24 | on, 25 | dragable, 26 | dragRadius, 27 | bounceRadius, 28 | open, 29 | bounceOnOpen, 30 | bounceOnClose, 31 | bounceDirection, 32 | } = props; 33 | 34 | if (on) { 35 | const [{ x, y, cursor }, set] = useSpring(() => ({ 36 | x: 0, 37 | y: 0, 38 | config: { tension: 400, friction: 7, precision: 0.1 }, 39 | cursor: "pointer", 40 | })); 41 | 42 | const classes = useStyles(props); 43 | 44 | React.useEffect(() => { 45 | if ((open && bounceOnOpen) || (!open && bounceOnClose)) { 46 | const bRadius = bounceRadius ? bounceRadius : DEFAULT_BOUNCE_RADIUS; 47 | let x = bRadius; 48 | let y = bRadius; 49 | switch (bounceDirection) { 50 | case "LEFT": 51 | x = -bRadius; 52 | y = 0; 53 | break; 54 | case "RIGHT": 55 | x = bRadius; 56 | y = 0; 57 | break; 58 | case "TOP": 59 | x = 0; 60 | y = -bRadius; 61 | break; 62 | case "BOTTOM": 63 | x = 0; 64 | y = bRadius; 65 | break; 66 | } 67 | set({ x, y }); 68 | setTimeout(() => set({ x: 0, y: 0 }), 100); 69 | } 70 | }, [open]); 71 | 72 | // Creates a drag gesture 73 | const bind = useDrag(({ down, movement: [dX, dY] }) => { 74 | if (dragable) { 75 | const rMax = dragRadius ? dragRadius : DEFAULT_DRAG_RADIUS; 76 | const r = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2)); 77 | // Find point along radius with rMax length 78 | // See: https://math.stackexchange.com/q/1630886 79 | if (r > rMax) { 80 | dX *= rMax / r; 81 | dY *= rMax / r; 82 | } 83 | set({ 84 | x: down ? dX : 0, 85 | y: down ? dY : 0, 86 | immediate: down, 87 | cursor: down ? "grabbing" : "pointer", 88 | }); 89 | } 90 | }); 91 | 92 | return ( 93 |
94 | 99 | {children} 100 | 101 |
102 | ); 103 | } else { 104 | return <>{children}; 105 | } 106 | } 107 | 108 | const useStyles = makeStyles({ 109 | root: { 110 | top: 0, 111 | left: 0, 112 | }, 113 | 114 | dragable: { 115 | position: "absolute", 116 | }, 117 | }); 118 | -------------------------------------------------------------------------------- /src/components/Orbit.tsx: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | import { makeStyles } from '@material-ui/core'; 3 | import { CreateCSSProperties, CSSProperties } from '@material-ui/styles'; 4 | import * as React from 'react'; 5 | import { animated, useSpring } from 'react-spring'; 6 | 7 | interface Props { 8 | orbitStyle?: (defaultStyle: CSSProperties | CreateCSSProperties<{}>) => CSSProperties | CreateCSSProperties<{}>; 9 | orbitRadius: number; 10 | planetWidth: number; 11 | planetHeight: number; 12 | open: boolean; 13 | mass: number; 14 | tension: number; 15 | friction: number; 16 | } 17 | 18 | export function Orbit(props: Props) { 19 | const { orbitRadius, planetWidth, planetHeight, open, tension, friction, mass } = props; 20 | const classes = useStyles(props); 21 | const position = useSpring({ 22 | reverse: !open, 23 | from: getInitalOrbitPosition(planetWidth, planetHeight), 24 | to: getFinalOrbitPosition(planetWidth, planetHeight, orbitRadius), 25 | config: { mass, tension, friction }, 26 | }); 27 | 28 | return ; 29 | } 30 | 31 | function getInitalOrbitPosition(planetWidth: number, planetHeight: number) { 32 | return { 33 | width: 0, 34 | height: 0, 35 | top: planetWidth / 2, 36 | left: planetHeight / 2, 37 | opacity: 0, 38 | }; 39 | } 40 | 41 | function getFinalOrbitPosition(planetWidth: number, planetHeight: number, orbitRadius: number) { 42 | return { 43 | width: orbitRadius * 2, 44 | height: orbitRadius * 2, 45 | top: 0 - orbitRadius + planetHeight / 2, 46 | left: 0 - orbitRadius + planetWidth / 2, 47 | opacity: 1, 48 | }; 49 | } 50 | 51 | const orbitDefaultStyle: CSSProperties | CreateCSSProperties<{}> = { 52 | position: 'absolute', 53 | borderRadius: '100%', 54 | borderWidth: 2, 55 | borderStyle: 'dotted', 56 | borderColor: 'lightgrey', 57 | zIndex: 0, 58 | }; 59 | 60 | const useStyles = makeStyles({ 61 | orbit: (props: Props) => 62 | props.orbitStyle ? (props.orbitStyle(orbitDefaultStyle) as any) : (orbitDefaultStyle as any), 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/Planet.tsx: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | import { ClickAwayListener, makeStyles } from '@material-ui/core'; 3 | import { CreateCSSProperties, CSSProperties } from "@material-ui/styles"; 4 | import * as React from "react"; 5 | import { ReactElement } from "react"; 6 | import useResizeObserver from "use-resize-observer"; 7 | import { DragableContainer } from "./DragableContainer"; 8 | import { Orbit } from "./Orbit"; 9 | import { Satellite } from "./Satellite"; 10 | 11 | const DEFAULT_MASS = 1; 12 | const DEFAULT_TENSTION = 500; 13 | const DEFAULT_FRICTION = 17; 14 | const DEFAULT_ROTATION = 0; 15 | const DEFAULT_RADIUS = 100; 16 | 17 | interface Props { 18 | centerContent?: React.ReactNode; 19 | children?: React.ReactNode; 20 | open?: boolean; 21 | onClick?: (e: React.MouseEvent) => void; 22 | mass?: number; 23 | tension?: number; 24 | friction?: number; 25 | orbitStyle?: ( 26 | defaultStyle: CSSProperties | CreateCSSProperties<{}> 27 | ) => CSSProperties | CreateCSSProperties<{}>; 28 | orbitRadius?: number; 29 | rotation?: number; 30 | hideOrbit?: boolean; 31 | autoClose?: boolean; 32 | onClose?: ( 33 | e: React.MouseEvent 34 | ) => void; 35 | dragablePlanet?: boolean; 36 | dragRadiusPlanet?: number; 37 | dragableSatellites?: boolean; 38 | dragRadiusSatellites?: number; 39 | bounceRadius?: number; 40 | bounce?: boolean; 41 | bounceOnOpen?: boolean; 42 | bounceOnClose?: boolean; 43 | bounceDirection?: "TOP" | "BOTTOM" | "LEFT" | "RIGHT"; 44 | satelliteOrientation?: "DEFAULT" | "INSIDE" | "OUTSIDE" | "READABLE"; 45 | } 46 | 47 | export function Planet(props: Props) { 48 | const { 49 | centerContent, 50 | children, 51 | open, 52 | onClick, 53 | mass, 54 | tension, 55 | friction, 56 | orbitRadius, 57 | rotation, 58 | orbitStyle, 59 | hideOrbit, 60 | onClose, 61 | autoClose, 62 | dragablePlanet, 63 | dragRadiusPlanet, 64 | dragableSatellites, 65 | dragRadiusSatellites, 66 | bounceRadius, 67 | bounce, 68 | bounceOnOpen, 69 | bounceOnClose, 70 | bounceDirection, 71 | satelliteOrientation, 72 | } = props; 73 | const classes = useStyles(props); 74 | const { ref, height = 0, width = 0 } = useResizeObserver(); 75 | const [_open, setOpen] = React.useState(!!open); 76 | 77 | React.useEffect(() => { 78 | setOpen(!!open); 79 | }, [open]); 80 | 81 | var satellites: ReactElement[] = []; 82 | var satelliteCount = React.Children.count(children); 83 | React.Children.forEach(children, (c, i) => { 84 | satellites[i] = ( 85 | 101 | {c} 102 | 103 | ); 104 | }); 105 | 106 | const onPlanet = (e: React.MouseEvent) => { 107 | if (onClick) { 108 | onClick(e); 109 | } else { 110 | if (_open && autoClose) { 111 | setOpen(false); 112 | if (onClose) { 113 | onClose(e); 114 | } 115 | } else { 116 | setOpen(true); 117 | } 118 | } 119 | }; 120 | 121 | const onClickAway = (e: React.MouseEvent) => { 122 | if (autoClose) { 123 | setOpen(false); 124 | } 125 | 126 | if (onClose && _open) { 127 | onClose(e); 128 | } 129 | }; 130 | 131 | return ( 132 | 133 |
134 | {!hideOrbit && ( 135 | 145 | )} 146 | <>{satellites} 147 |
148 | 160 |
{centerContent}
161 |
162 |
163 |
164 |
165 | ); 166 | } 167 | 168 | const useStyles = makeStyles({ 169 | root: { 170 | position: "relative", 171 | }, 172 | 173 | planetContent: { 174 | position: "absolute", 175 | zIndex: 1, 176 | }, 177 | }); 178 | -------------------------------------------------------------------------------- /src/components/Satellite.tsx: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | import { makeStyles } from '@material-ui/core'; 3 | import * as React from "react"; 4 | import { animated, useSpring } from "react-spring"; 5 | import useResizeObserver from "use-resize-observer"; 6 | import { DragableContainer } from "./DragableContainer"; 7 | 8 | interface Props { 9 | index: number; 10 | open: boolean; 11 | satelliteCount: number; 12 | children?: React.ReactNode; 13 | planetWidth: number; 14 | planetHeight: number; 15 | mass: number; 16 | tension: number; 17 | friction: number; 18 | orbitRadius: number; 19 | rotation: number; 20 | dragable: boolean; 21 | dragRadius?: number; 22 | orientation?: "DEFAULT" | "INSIDE" | "OUTSIDE" | "READABLE"; 23 | } 24 | 25 | export function Satellite(props: Props) { 26 | const { 27 | children, 28 | index, 29 | satelliteCount, 30 | open, 31 | planetWidth, 32 | planetHeight, 33 | tension, 34 | friction, 35 | mass, 36 | orbitRadius, 37 | rotation, 38 | dragable, 39 | dragRadius, 40 | orientation, 41 | } = props; 42 | const classes = useStyles(props); 43 | const { ref, height = 0, width = 0 } = useResizeObserver(); 44 | const position = useSpring({ 45 | reverse: !open, 46 | from: getInitalSatellitePosition(width, height, planetWidth, planetHeight), 47 | to: getFinalSatellitePosition( 48 | index, 49 | satelliteCount, 50 | width, 51 | height, 52 | planetWidth, 53 | planetHeight, 54 | orbitRadius, 55 | rotation, 56 | orientation 57 | ), 58 | config: { mass, tension, friction }, 59 | }); 60 | 61 | return ( 62 | 63 | 68 |
{children}
69 |
70 |
71 | ); 72 | } 73 | 74 | function getFinalSatellitePosition( 75 | index: number, 76 | satelliteCount: number, 77 | width: number, 78 | height: number, 79 | planetWidth: number, 80 | planetHeight: number, 81 | orbitRadius: number, 82 | rotation: number, 83 | orientation: "DEFAULT" | "INSIDE" | "OUTSIDE" | "READABLE" | undefined 84 | ) { 85 | let { deltaX, deltaY, angle } = getFinalDeltaPositions( 86 | index, 87 | satelliteCount, 88 | width, 89 | height, 90 | orbitRadius, 91 | rotation 92 | ); 93 | 94 | let transform = {}; 95 | switch (orientation) { 96 | case "OUTSIDE": 97 | transform = { transform: "rotate(" + angle + "deg)" }; 98 | break; 99 | case "INSIDE": 100 | transform = { transform: "rotate(" + (angle + 180) + "deg)" }; 101 | break; 102 | case "READABLE": 103 | transform = 104 | angle > 90 && angle < 270 105 | ? { transform: "rotate(" + (angle + 180) + "deg)" } 106 | : { transform: "rotate(" + angle + "deg)" }; 107 | break; 108 | default: 109 | transform = { transform: "rotate(" + 0 + "deg)" }; 110 | } 111 | 112 | return { 113 | top: planetHeight / 2 + deltaX, 114 | left: planetWidth / 2 - deltaY, 115 | opacity: 1, 116 | ...transform, 117 | }; 118 | } 119 | 120 | function getInitalSatellitePosition( 121 | width: number, 122 | height: number, 123 | planetWidth: number, 124 | planetHeight: number 125 | ) { 126 | return { 127 | top: planetHeight / 2 - height / 2, 128 | left: planetWidth / 2 - width / 2, 129 | opacity: 0, 130 | }; 131 | } 132 | 133 | function getFinalDeltaPositions( 134 | index: number, 135 | satelliteCount: number, 136 | width: number, 137 | height: number, 138 | orbitRadius: number, 139 | rotation: number 140 | ) { 141 | const SEPARATION_ANGLE = 360 / satelliteCount; 142 | const FAN_ANGLE = (satelliteCount - 1) * SEPARATION_ANGLE; 143 | const BASE_ANGLE = (180 - FAN_ANGLE) / 2 + 90 + rotation; 144 | 145 | let TARGET_ANGLE = BASE_ANGLE + index * SEPARATION_ANGLE; 146 | return { 147 | deltaX: orbitRadius * Math.cos(toRadians(TARGET_ANGLE)) - height / 2, 148 | deltaY: orbitRadius * Math.sin(toRadians(TARGET_ANGLE)) + width / 2, 149 | angle: TARGET_ANGLE, 150 | }; 151 | } 152 | 153 | const useStyles = makeStyles({ 154 | root: (props: Props) => ({ 155 | position: "absolute", 156 | zIndex: props.open ? 2 : 0, 157 | }), 158 | }); 159 | 160 | // UTILITY FUNCTIONS 161 | const DEG_TO_RAD = 0.0174533; 162 | function toRadians(degrees: number) { 163 | return degrees * DEG_TO_RAD; 164 | } 165 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Planet'; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Planet } from './components'; 2 | -------------------------------------------------------------------------------- /src/stories/Configurator.stories.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme } from '@material-ui/core'; 2 | import { boolean, number, select } from '@storybook/addon-knobs'; 3 | import { storiesOf } from '@storybook/react'; 4 | import * as React from 'react'; 5 | import { Planet } from '../components'; 6 | import { withTheme } from './index.stories'; 7 | import { generateSatellites } from './storybook_utils.tsx/generateSatellites'; 8 | 9 | storiesOf('Planet', module).add('-> configure', () => { 10 | const classes = useStyles(); 11 | 12 | const open = boolean('open', true); 13 | const autoClose = boolean('autoClose', true); 14 | const orbitRadius = number('orbit Radius', 120, { range: true, min: 80, max: 300, step: 10 }); 15 | const satelliteCount = number('satellite count', 3, {}); 16 | const weirdSatellites = boolean('weird Satellites', false); 17 | const satelliteOrientation = select( 18 | 'satellite Orientation', 19 | { 20 | unset: undefined, 21 | DEFAULT: 'DEFAULT', 22 | INSIDE: 'INSIDE', 23 | OUTSIDE: 'OUTSIDE', 24 | READABLE: 'READABLE', 25 | }, 26 | undefined 27 | ); 28 | const dragableSatellites = boolean('dragable Satellites', false); 29 | const dragRadiusSatellites = number('dragRadius Satellites', 12, { range: true, min: 0, max: 100, step: 1 }); 30 | const dragablePlanet = boolean('dragable Planet', false); 31 | const dragRadiusPlanet = number('dragRadius Planet', 12, { range: true, min: 0, max: 100, step: 1 }); 32 | const hideOrbit = boolean('hide Orbit', false); 33 | const bounce = boolean('bounce', false); 34 | const bounceOnOpen = boolean('bounce on open', false); 35 | const bounceOnClose = boolean('bounce on close', false); 36 | const bounceRadius = number('bounce Radius', 3, { range: true, min: 1, max: 40, step: 1 }); 37 | const bounceDirection = select( 38 | 'bounce Direction', 39 | { 40 | unset: undefined, 41 | TOP: 'TOP', 42 | BOTTOM: 'BOTTOM', 43 | LEFT: 'LEFT', 44 | RIGHT: 'RIGHT', 45 | }, 46 | undefined 47 | ); 48 | 49 | const rotation = number('rotation', 0, { range: true, min: 0, max: 360, step: 5 }); 50 | const mass = number('mass', 1, {}); 51 | const tension = number('tension', 500, {}); 52 | const friction = number('friction', 17, {}); 53 | 54 | return withTheme( 55 |
56 | } 59 | open={open} 60 | autoClose={autoClose} 61 | dragableSatellites={dragableSatellites} 62 | dragRadiusSatellites={dragRadiusSatellites} 63 | dragablePlanet={dragablePlanet} 64 | dragRadiusPlanet={dragRadiusPlanet} 65 | hideOrbit={hideOrbit} 66 | rotation={rotation} 67 | mass={mass} 68 | tension={tension} 69 | friction={friction} 70 | bounce={bounce} 71 | bounceOnOpen={bounceOnOpen} 72 | bounceOnClose={bounceOnClose} 73 | bounceRadius={bounceRadius} 74 | bounceDirection={bounceDirection} 75 | satelliteOrientation={satelliteOrientation} 76 | > 77 | {generateSatellites(satelliteCount, weirdSatellites, !!satelliteOrientation)} 78 | 79 |
80 | ); 81 | }); 82 | 83 | const useStyles = makeStyles((theme: Theme) => ({ 84 | root: { 85 | display: 'flex', 86 | flex: 1, 87 | width: '100%', 88 | backgroundColor: 'white', 89 | justifyContent: 'center', 90 | alignItems: 'center', 91 | minHeight: 600, 92 | }, 93 | 94 | planet: { 95 | height: 100, 96 | width: 100, 97 | borderRadius: '50%', 98 | backgroundColor: theme.palette.primary.main, 99 | '&:hover': { 100 | borderWidth: 10, 101 | boxShadow: '0px 0px 15px 10px ' + theme.palette.secondary.light, 102 | cursor: 'pointer', 103 | }, 104 | }, 105 | })); 106 | -------------------------------------------------------------------------------- /src/stories/CustomOrbit.stories.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme } from '@material-ui/core'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import { Planet } from '../components'; 5 | import { withTheme } from './index.stories'; 6 | import { generateSatellites } from './storybook_utils.tsx/generateSatellites'; 7 | 8 | storiesOf('Planet', module).add('custom orbit', () => { 9 | const classes = useStyles(); 10 | 11 | return withTheme( 12 |
13 | ({ 15 | ...defaultStyle, 16 | borderWidth: 4, 17 | borderStyle: 'dashed', 18 | borderColor: '#6f03fc', 19 | })} 20 | centerContent={
} 21 | open 22 | orbitRadius={200} 23 | autoClose 24 | > 25 | {generateSatellites(3)} 26 | 27 |
28 | ); 29 | }); 30 | 31 | const useStyles = makeStyles((theme: Theme) => ({ 32 | root: { 33 | display: 'flex', 34 | flex: 1, 35 | width: '100%', 36 | backgroundColor: 'white', 37 | justifyContent: 'center', 38 | alignItems: 'center', 39 | minHeight: 600, 40 | }, 41 | 42 | planetSmall: { 43 | height: 10, 44 | width: 10, 45 | backgroundColor: theme.palette.primary.main, 46 | borderRadius: '50%', 47 | '&:hover': { 48 | borderWidth: 10, 49 | boxShadow: '0px 0px 15px 10px ' + theme.palette.secondary.light, 50 | cursor: 'pointer', 51 | }, 52 | }, 53 | })); 54 | -------------------------------------------------------------------------------- /src/stories/Half.stories.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme } from '@material-ui/core'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import { Planet } from '../components'; 5 | import { withTheme } from './index.stories'; 6 | import { generateSatellites } from './storybook_utils.tsx/generateSatellites'; 7 | 8 | storiesOf('Planet', module).add('half', () => { 9 | const classes = useStyles(); 10 | 11 | return withTheme( 12 |
13 | } hideOrbit open autoClose rotation={90}> 14 | {generateSatellites(3)} 15 |
16 |
17 |
18 | 19 |
20 | ); 21 | }); 22 | 23 | const useStyles = makeStyles((theme: Theme) => ({ 24 | root: { 25 | display: 'flex', 26 | flex: 1, 27 | width: '100%', 28 | backgroundColor: 'white', 29 | justifyContent: 'center', 30 | alignItems: 'center', 31 | minHeight: 600, 32 | }, 33 | 34 | planet: { 35 | height: 100, 36 | width: 100, 37 | borderRadius: '50%', 38 | backgroundColor: theme.palette.primary.main, 39 | '&:hover': { 40 | borderWidth: 10, 41 | boxShadow: '0px 0px 15px 10px ' + theme.palette.secondary.light, 42 | cursor: 'pointer', 43 | }, 44 | }, 45 | })); 46 | -------------------------------------------------------------------------------- /src/stories/Menu.stories.tsx: -------------------------------------------------------------------------------- 1 | import { IconButton, makeStyles, Paper, Theme } from '@material-ui/core'; 2 | import DeleteIcon from '@material-ui/icons/Delete'; 3 | import EditIcon from '@material-ui/icons/Edit'; 4 | import InfoIcon from '@material-ui/icons/InfoOutlined'; 5 | import MenuIcon from '@material-ui/icons/Menu'; 6 | import { boolean } from '@storybook/addon-knobs'; 7 | import { storiesOf } from '@storybook/react'; 8 | import * as React from 'react'; 9 | import { Planet } from '../components'; 10 | import { withTheme } from './index.stories'; 11 | 12 | storiesOf('Planet', module).add('menu', () => { 13 | const classes = useStyles(); 14 | 15 | const bounce = boolean('bounce', false); 16 | const bounceOnClose = boolean('bounce on close', false); 17 | 18 | return withTheme( 19 |
20 | 23 | 24 | 25 | } 26 | hideOrbit 27 | autoClose 28 | orbitRadius={60} 29 | bounce={bounce} 30 | bounceOnClose={bounceOnClose} 31 | > 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | ); 44 | }); 45 | 46 | function PaperButton(props: { color: string; children: React.ReactNode; iconColor: 'LIGHT' | 'DARK' }) { 47 | return ( 48 | 49 | 50 | {props.children} 51 | 52 | 53 | ); 54 | } 55 | 56 | const useStyles = makeStyles((theme: Theme) => ({ 57 | root: { 58 | display: 'flex', 59 | flex: 1, 60 | width: '100%', 61 | backgroundColor: 'white', 62 | justifyContent: 'center', 63 | alignItems: 'center', 64 | minHeight: 600, 65 | }, 66 | })); 67 | -------------------------------------------------------------------------------- /src/stories/Planetception.stories.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme } from "@material-ui/core"; 2 | import { storiesOf } from "@storybook/react"; 3 | import * as React from "react"; 4 | import { Planet } from "../components"; 5 | import { withTheme } from "./index.stories"; 6 | import { generateSatellites } from "./storybook_utils.tsx/generateSatellites"; 7 | 8 | storiesOf("Planet", module).add("planetception (planets as satellites)", () => { 9 | const classes = useStyles(); 10 | 11 | return withTheme( 12 |
13 | } 15 | open 16 | orbitRadius={220} 17 | > 18 | } 20 | autoClose 21 | > 22 | {generateSatellites(3)} 23 | 24 | } 26 | autoClose 27 | open 28 | > 29 | {generateSatellites(3)} 30 | 31 | } 33 | autoClose 34 | > 35 | {generateSatellites(3)} 36 | 37 | 38 |
39 | ); 40 | }); 41 | 42 | const useStyles = makeStyles((theme: Theme) => ({ 43 | root: { 44 | display: "flex", 45 | flex: 1, 46 | width: "100%", 47 | backgroundColor: "white", 48 | justifyContent: "center", 49 | alignItems: "center", 50 | minHeight: 600, 51 | }, 52 | planet: { 53 | height: 100, 54 | width: 100, 55 | borderRadius: "50%", 56 | backgroundColor: theme.palette.primary.main, 57 | "&:hover": { 58 | borderWidth: 10, 59 | boxShadow: "0px 0px 15px 10px " + theme.palette.secondary.light, 60 | cursor: "pointer", 61 | }, 62 | }, 63 | 64 | planetception: { 65 | position: "absolute", 66 | top: -15, 67 | left: -15, 68 | height: 30, 69 | width: 30, 70 | backgroundColor: theme.palette.secondary.main, 71 | borderRadius: "50%", 72 | "&:hover": { 73 | borderWidth: 10, 74 | boxShadow: "0px 0px 15px 10px " + theme.palette.secondary.light, 75 | cursor: "pointer", 76 | }, 77 | }, 78 | })); 79 | -------------------------------------------------------------------------------- /src/stories/WeirdPlanet.stories.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme } from '@material-ui/core'; 2 | import { storiesOf } from '@storybook/react'; 3 | import * as React from 'react'; 4 | import { Planet } from '../components'; 5 | import { withTheme } from './index.stories'; 6 | import { generateSatellites } from './storybook_utils.tsx/generateSatellites'; 7 | 8 | storiesOf('Planet', module).add('weird planet', () => { 9 | const classes = useStyles(); 10 | 11 | return withTheme( 12 |
13 | } open orbitRadius={90} autoClose> 14 | {generateSatellites(6)} 15 | 16 |
17 | ); 18 | }); 19 | 20 | const useStyles = makeStyles((theme: Theme) => ({ 21 | root: { 22 | display: 'flex', 23 | flex: 1, 24 | width: '100%', 25 | backgroundColor: 'white', 26 | justifyContent: 'center', 27 | alignItems: 'center', 28 | minHeight: 600, 29 | }, 30 | 31 | planetWeird: { 32 | height: 30, 33 | width: 90, 34 | backgroundColor: theme.palette.primary.dark, 35 | borderRadius: '10%', 36 | '&:hover': { 37 | backgroundColor: theme.palette.primary.main, 38 | cursor: 'pointer', 39 | }, 40 | }, 41 | })); 42 | -------------------------------------------------------------------------------- /src/stories/WeirdSatellites.stories.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme } from '@material-ui/core'; 2 | import { select } from '@storybook/addon-knobs'; 3 | import { storiesOf } from '@storybook/react'; 4 | import * as React from 'react'; 5 | import { Planet } from '../components'; 6 | import { withTheme } from './index.stories'; 7 | import { generateSatellites } from './storybook_utils.tsx/generateSatellites'; 8 | 9 | storiesOf('Planet', module).add('weird satelites', () => { 10 | const classes = useStyles(); 11 | 12 | const satelliteOrientation = select( 13 | 'satellite Orientation', 14 | { 15 | unset: undefined, 16 | DEFAULT: 'DEFAULT', 17 | INSIDE: 'INSIDE', 18 | OUTSIDE: 'OUTSIDE', 19 | READABLE: 'READABLE', 20 | }, 21 | 'READABLE' 22 | ); 23 | 24 | return withTheme( 25 |
26 | } 28 | orbitRadius={200} 29 | open 30 | autoClose 31 | satelliteOrientation={satelliteOrientation} 32 | > 33 | {generateSatellites(9, true, !!satelliteOrientation)} 34 | 35 |
36 | ); 37 | }); 38 | 39 | const useStyles = makeStyles((theme: Theme) => ({ 40 | root: { 41 | display: 'flex', 42 | flex: 1, 43 | width: '100%', 44 | backgroundColor: 'white', 45 | justifyContent: 'center', 46 | alignItems: 'center', 47 | minHeight: 600, 48 | }, 49 | 50 | planet: { 51 | height: 100, 52 | width: 100, 53 | borderRadius: '50%', 54 | backgroundColor: theme.palette.primary.main, 55 | '&:hover': { 56 | borderWidth: 10, 57 | boxShadow: '0px 0px 15px 10px ' + theme.palette.secondary.light, 58 | cursor: 'pointer', 59 | }, 60 | }, 61 | })); 62 | -------------------------------------------------------------------------------- /src/stories/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { createMuiTheme, CssBaseline } from '@material-ui/core'; 2 | import { ThemeProvider } from '@material-ui/styles'; 3 | import * as React from 'react'; 4 | import { scale } from './storybook_utils.tsx/utils'; 5 | 6 | const theme = createMuiTheme({ 7 | palette: { 8 | primary: { 9 | light: '#5593e6', 10 | main: '#0066B3', 11 | dark: '#003c83', 12 | contrastText: '#fff', 13 | }, 14 | secondary: { 15 | light: '#ff983f', 16 | main: '#FF6600', 17 | dark: '#c43300', 18 | contrastText: '#fff', 19 | }, 20 | }, 21 | typography: { 22 | fontSize: scale(14, 11, 14), 23 | }, 24 | }); 25 | export const withTheme = (component: any) => { 26 | return ( 27 | 28 | {/* Reboot kickstart an elegant, consistent, and simple baseline to build upon. */} 29 | 30 | {component} 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/stories/storybook_utils.tsx/generateSatellites.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme, Typography } from '@material-ui/core'; 2 | import * as React from 'react'; 3 | 4 | export function generateSatellites(count: number, weird?: boolean, text?: boolean) { 5 | const classes = useStyles(); 6 | 7 | const satellites = []; 8 | let only3 = 1; 9 | 10 | for (let i = 0; i < count; i++) { 11 | if (weird) { 12 | satellites.push( 13 |
23 | {text && {only3 === 1 ? '1' : only3 === 2 ? '2' : '3'}} 24 |
25 | ); 26 | } else { 27 | satellites.push( 28 |
32 | {text && {i + 1}} 33 |
34 | ); 35 | } 36 | 37 | only3++; 38 | if (only3 > 3) { 39 | only3 = 1; 40 | } 41 | } 42 | 43 | return satellites; 44 | } 45 | 46 | const useStyles = makeStyles((theme: Theme) => ({ 47 | satellite1: { 48 | height: 50, 49 | width: 50, 50 | borderRadius: '50%', 51 | backgroundColor: theme.palette.secondary.main, 52 | display: 'flex', 53 | justifyContent: 'center', 54 | alignItems: 'center', 55 | color: 'white', 56 | }, 57 | 58 | satellite2: { 59 | height: 50, 60 | width: 50, 61 | borderRadius: '50%', 62 | backgroundColor: theme.palette.secondary.dark, 63 | display: 'flex', 64 | justifyContent: 'center', 65 | alignItems: 'center', 66 | color: 'white', 67 | }, 68 | 69 | satellite3: { 70 | height: 50, 71 | width: 50, 72 | borderRadius: '50%', 73 | backgroundColor: theme.palette.secondary.light, 74 | display: 'flex', 75 | justifyContent: 'center', 76 | alignItems: 'center', 77 | color: 'white', 78 | }, 79 | 80 | satelliteWeird1: { 81 | height: 120, 82 | width: 20, 83 | backgroundColor: '#f5e298', 84 | display: 'flex', 85 | justifyContent: 'center', 86 | alignItems: 'center', 87 | color: '#424242', 88 | }, 89 | 90 | satelliteWeird2: { 91 | height: 30, 92 | width: 100, 93 | borderRadius: '25%', 94 | backgroundColor: '#db7948', 95 | display: 'flex', 96 | justifyContent: 'center', 97 | alignItems: 'center', 98 | color: 'white', 99 | }, 100 | 101 | satelliteWeird3: { 102 | height: 50, 103 | width: 80, 104 | borderRadius: '50%', 105 | backgroundColor: '#aabf73', 106 | display: 'flex', 107 | justifyContent: 'center', 108 | alignItems: 'center', 109 | color: 'white', 110 | }, 111 | })); 112 | -------------------------------------------------------------------------------- /src/stories/storybook_utils.tsx/utils.ts: -------------------------------------------------------------------------------- 1 | export function scale(size?: number, min?: number, max?: number) { 2 | const w = window.innerWidth; 3 | const h = window.innerHeight; 4 | const zw = w / 1200; 5 | const zh = h / 900; 6 | 7 | const scaleFactor = zw <= zh ? zw : zh; 8 | 9 | if (!size) { 10 | return scaleFactor; 11 | } 12 | 13 | const s = size * scaleFactor; 14 | 15 | if (min && min > s) { 16 | return min; 17 | } 18 | if (max && max < s) { 19 | return max; 20 | } 21 | 22 | return s; 23 | } 24 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@storybook/*'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom", "es2016", "es2017"], 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "jsx": "react", 10 | "declaration": true, 11 | "moduleResolution": "node", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "noUnusedLocals": false, 19 | "noUnusedParameters": false, 20 | "esModuleInterop": true, 21 | "skipLibCheck": true 22 | }, 23 | "include": ["src"], 24 | "exclude": ["node_modules", "build", "dist", "example", "rollup.config.js", "src/stories/*"] 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | --------------------------------------------------------------------------------