├── .gitignore ├── package.json └── pages └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | package-lock.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cards", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "next" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "next": "^8.0.3", 14 | "react": "^16.8.6", 15 | "react-dom": "^16.8.6" 16 | }, 17 | "devDependencies": { 18 | "babel-plugin-styled-components": "^1.10.0", 19 | "styled-components": "^4.2.0" 20 | }, 21 | "babel": { 22 | "env": { 23 | "development": { 24 | "presets": ["next/babel"], 25 | "plugins": [ 26 | [ 27 | "styled-components", 28 | { 29 | "ssr": true, 30 | "displayName": true 31 | } 32 | ] 33 | ] 34 | }, 35 | "production": { 36 | "presets": ["next/babel"], 37 | "plugins": [ 38 | [ 39 | "styled-components", 40 | { 41 | "ssr": true, 42 | "displayName": false 43 | } 44 | ] 45 | ] 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState, createContext, useReducer, useMemo } from 'react' 2 | import styled from 'styled-components' 3 | import Head from 'next/head' 4 | import { getDiffieHellman } from 'crypto'; 5 | 6 | const Container = styled.div` 7 | height: 100%; 8 | position: relative; 9 | ` 10 | 11 | const CARD_WIDTH = 180 12 | const CARD_HEIGHT = 250 13 | 14 | const CardElem = styled.div` 15 | width: ${ CARD_WIDTH }px; 16 | height: ${ CARD_HEIGHT }px; 17 | background: blue; 18 | border-radius: 14px; 19 | background: radial-gradient(#fafafa, #eee); 20 | box-shadow: 0 8px 20px -12px #eee; 21 | position: absolute; 22 | text-align: center; 23 | font: 72px/${ CARD_HEIGHT }px Verdana, sans-serif; 24 | color: #ddd; 25 | user-select: none; 26 | ` 27 | const Card = ({ id, x, y, active }) => { 28 | return 34 | { id.slice(0, 1).toUpperCase() } 35 | 36 | } 37 | 38 | const Pre = styled.pre` 39 | width: 600px; 40 | height: 800px; 41 | overflow-y: auto; 42 | user-select: none; 43 | ` 44 | 45 | const CardContext = createContext() 46 | 47 | export default function Index() { 48 | const [ state, setState ] = useReducer( 49 | (state, newState) => { 50 | return { ...state, ...newState } 51 | }, 52 | { 53 | cards: [], 54 | drag: null, 55 | diff: { 56 | x: 0, 57 | y: 0, 58 | } 59 | } 60 | ) 61 | 62 | const contextState = useMemo( 63 | () => ({ 64 | ...state, 65 | setState, 66 | createCard: () => { 67 | setState({ 68 | cards: [ 69 | ...state.cards, 70 | { 71 | key: Math.random().toString(36).slice(2), 72 | x: Math.ceil(Math.random() * 500), 73 | y: Math.ceil(Math.random() * 500), 74 | active: false, 75 | } 76 | ] 77 | }) 78 | } 79 | }), 80 | [ state.cards, state.drag, state.mouse, state.diff ], 81 | ) 82 | 83 | const onMouseDown = ({ clientX, clientY }) => { 84 | let index; 85 | let card; 86 | 87 | for (index in state.cards.slice().reverse()) { 88 | if ( 89 | clientX >= state.cards[index].x && 90 | clientX <= state.cards[index].x + CARD_WIDTH && 91 | clientY >= state.cards[index].y && 92 | clientY <= state.cards[index].y + CARD_HEIGHT 93 | ) { 94 | card = state.cards[index] 95 | break 96 | } 97 | } 98 | 99 | if (card) { 100 | setState({ 101 | drag: parseInt(index), 102 | diff: { 103 | x: clientX - card.x, 104 | y: clientY - card.y, 105 | }, 106 | cards: state.cards.map((item, i) => i !== parseInt(index) ? item : { 107 | ...item, 108 | active: true, 109 | }) 110 | }) 111 | } 112 | } 113 | 114 | const onMouseUp = () => { 115 | setState({ 116 | drag: null, 117 | diff: { x: 0, y: 0}, 118 | cards: state.cards.map((item, i) => i !== state.drag ? item : { 119 | ...item, 120 | active: false, 121 | }) 122 | }) 123 | } 124 | 125 | const onMouseMove = ({ clientX, clientY }) => { 126 | if (state.drag !== null) { 127 | setState({ 128 | cards: state.cards.map((item, i) => i !== state.drag ? item : { 129 | ...item, 130 | x: clientX - state.diff.x, 131 | y: clientY - state.diff.y, 132 | }) 133 | }) 134 | } 135 | } 136 | 137 | return ( 138 | 139 | 140 | 141 | 144 | 145 |
{ JSON.stringify(state, null, 2) }
146 | 147 | { 148 | state.cards.map(({ key, x, y, active }) => ) 149 | } 150 |
151 |
152 | ) 153 | } --------------------------------------------------------------------------------