├── .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 | [](https://www.npmjs.com/package/react-planet)
4 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------