From 42ccbe9456e75d3c0d921daf083a11e07fffa3bb Mon Sep 17 00:00:00 2001 From: maker27 Date: Mon, 8 Nov 2021 22:28:01 +0300 Subject: [PATCH] ready project --- README.md | 9 + package.json | 13 +- src/assets/boxes.ts | 12 + src/assets/constants.ts | 12 + src/assets/types.ts | 12 + src/components/App/App.scss | 81 + src/components/App/index.tsx | 84 + src/components/Button/Button.scss | 37 + src/components/Button/index.tsx | 12 + src/components/ButtonBox/ButtonBox.scss | 28 + src/components/ButtonBox/index.tsx | 71 + src/components/Calculator/Calculator.scss | 20 + src/components/Calculator/index.tsx | 78 + src/components/Sidebar/Sidebar.scss | 13 + src/components/Sidebar/index.tsx | 10 + src/components/Switcher/Switcher.scss | 32 + src/components/Switcher/index.tsx | 35 + src/fonts/icons/icons.eot | Bin 0 -> 1696 bytes src/fonts/icons/icons.svg | 12 + src/fonts/icons/icons.ttf | Bin 0 -> 1540 bytes src/fonts/icons/icons.woff | Bin 0 -> 1616 bytes src/images/drag-and-drop.png | Bin 0 -> 2781 bytes src/images/icon.png | Bin 0 -> 10780 bytes src/index.scss | 37 + src/index.tsx | 8 +- src/store/calculatorSlice.ts | 51 + src/store/constructionSlice.ts | 34 + src/store/index.ts | 13 + src/utils.ts | 3 + tests/calculatorSlice.test.ts | 69 + tests/constructionSlice.test.ts | 48 + webpack.dev.config.ts | 3 +- webpack.prod.config.ts | 20 +- yarn.lock | 1790 ++++++++++++++++++++- 34 files changed, 2571 insertions(+), 76 deletions(-) create mode 100644 README.md create mode 100644 src/assets/boxes.ts create mode 100644 src/assets/constants.ts create mode 100644 src/assets/types.ts create mode 100644 src/components/App/App.scss create mode 100644 src/components/App/index.tsx create mode 100644 src/components/Button/Button.scss create mode 100644 src/components/Button/index.tsx create mode 100644 src/components/ButtonBox/ButtonBox.scss create mode 100644 src/components/ButtonBox/index.tsx create mode 100644 src/components/Calculator/Calculator.scss create mode 100644 src/components/Calculator/index.tsx create mode 100644 src/components/Sidebar/Sidebar.scss create mode 100644 src/components/Sidebar/index.tsx create mode 100644 src/components/Switcher/Switcher.scss create mode 100644 src/components/Switcher/index.tsx create mode 100644 src/fonts/icons/icons.eot create mode 100644 src/fonts/icons/icons.svg create mode 100644 src/fonts/icons/icons.ttf create mode 100644 src/fonts/icons/icons.woff create mode 100644 src/images/drag-and-drop.png create mode 100644 src/images/icon.png create mode 100644 src/store/calculatorSlice.ts create mode 100644 src/store/constructionSlice.ts create mode 100644 src/store/index.ts create mode 100644 src/utils.ts create mode 100644 tests/calculatorSlice.test.ts create mode 100644 tests/constructionSlice.test.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4f4476 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Конструктор калькулятора + +[Демоверсия](https://maker27.github.io/calculator-constructor/) (github pages) + +## Стэк: +- TypeScript, React +- Redux, Redux Toolkit +- Scss +- react-beautiful-dnd (drag and drop) \ No newline at end of file diff --git a/package.json b/package.json index ace6e92..be4dfab 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.0.1", "scripts": { "start": "webpack serve --config webpack.dev.config.ts", - "build": "webpack --config webpack.prod.config.ts" + "build": "webpack --config webpack.prod.config.ts", + "test": "jest", + "deploy": "gh-pages -d build" }, "dependencies": { "react": "^17.0.1", @@ -16,9 +18,12 @@ "@babel/preset-react": "^7.12.10", "@babel/preset-typescript": "^7.12.7", "@babel/runtime": "^7.12.5", + "@reduxjs/toolkit": "^1.6.2", "@types/fork-ts-checker-webpack-plugin": "^0.4.5", + "@types/jest": "^27.0.2", "@types/mini-css-extract-plugin": "^2.0.1", "@types/react": "^17.0.0", + "@types/react-beautiful-dnd": "^13.1.2", "@types/react-dom": "^17.0.0", "@types/webpack": "^4.41.25", "@types/webpack-dev-server": "^3.11.1", @@ -33,11 +38,17 @@ "eslint-webpack-plugin": "^2.4.1", "file-loader": "^6.2.0", "fork-ts-checker-webpack-plugin": "^6.0.8", + "gh-pages": "^3.2.3", "html-webpack-plugin": "^4.5.1", + "jest": "^27.3.1", "mini-css-extract-plugin": "^2.1.0", + "react-beautiful-dnd": "^13.1.0", + "react-redux": "^7.2.6", + "redux": "^4.1.2", "sass": "^1.43.4", "sass-loader": "^12.3.0", "style-loader": "^3.0.0", + "ts-jest": "^27.0.7", "ts-node": "^9.1.1", "typescript": "^4.1.3", "webpack": "^5.11.1", diff --git a/src/assets/boxes.ts b/src/assets/boxes.ts new file mode 100644 index 0000000..f13e6b2 --- /dev/null +++ b/src/assets/boxes.ts @@ -0,0 +1,12 @@ +import { DigitsBox, DisplayBox, OperationsBox, ResultBox } from '../components/ButtonBox'; + +const boxes = { + display: DisplayBox, + operations: OperationsBox, + digits: DigitsBox, + result: ResultBox +}; + +export type TBoxType = keyof typeof boxes; + +export default boxes; diff --git a/src/assets/constants.ts b/src/assets/constants.ts new file mode 100644 index 0000000..a4e12fc --- /dev/null +++ b/src/assets/constants.ts @@ -0,0 +1,12 @@ +import { IOperations } from './types'; + +export const operations: IOperations = { + '/': (a, b) => a / b, + // prettier-ignore + 'х': (a, b) => a * b, + '-': (a, b) => a - b, + '+': (a, b) => a + b, + '=': null +}; + +export const dot = '.'; diff --git a/src/assets/types.ts b/src/assets/types.ts new file mode 100644 index 0000000..9b86d1b --- /dev/null +++ b/src/assets/types.ts @@ -0,0 +1,12 @@ +type TOperationFunction = ((a: number, b: number) => number) | null; + +export interface IOperations { + '/': TOperationFunction; + // prettier-ignore + 'х': TOperationFunction; + '-': TOperationFunction; + '+': TOperationFunction; + '=': TOperationFunction; +} + +export type TOperation = keyof IOperations; diff --git a/src/components/App/App.scss b/src/components/App/App.scss new file mode 100644 index 0000000..e27059c --- /dev/null +++ b/src/components/App/App.scss @@ -0,0 +1,81 @@ +html { + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + display: flex; + min-height: 100%; + justify-content: center; + align-items: center; + background-color: #e5e5e5; + + @media (max-width: 550px) { + height: 100%; + overflow-y: auto; + } +} + +#root { + @media (max-width: 550px) { + width: 100%; + height: 100%; + } +} + +.container { + display: flex; + justify-content: center; + align-items: center; + width: 695px; + height: 640px; + background: #ffffff; + font: 500 14px/15px "Inter", sans-serif; + + &.runtime-mode { + .aside { + visibility: hidden; + } + .main .button:not(.button-display) { + cursor: pointer; + + &:hover, + &:active { + border: 2px solid #5d5fef; + } + } + } + @media (max-width: 550px) { + width: 100%; + height: max-content; + flex-direction: column; + justify-content: flex-start; + padding: 20px 0; + } +} + +.aside, +.main { + width: 243px; + height: 514px; +} + +.aside { + display: flex; + flex-direction: column; + justify-content: flex-end; + margin-right: 56px; + + @media (max-width: 550px) { + order: 1; + margin-right: 0; + } +} + +.main { + display: flex; + justify-content: space-between; + align-items: stretch; + flex-direction: column; +} diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx new file mode 100644 index 0000000..f386ae4 --- /dev/null +++ b/src/components/App/index.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import './App.scss'; +import { Sidebar } from '../Sidebar'; +import { Box } from '../ButtonBox'; +import Switcher from '../Switcher'; +import Calculator from '../Calculator'; +import boxes, { TBoxType } from '../../assets/boxes'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../store'; +import { setItems, toggleMode } from '../../store/constructionSlice'; +import { combineClassnames } from '../../utils'; +import { + DragDropContext, + Droppable, + Draggable, + DroppableProvided, + DraggableProvided, + DropResult +} from 'react-beautiful-dnd'; + +function App(): React.ReactElement { + const { items, mode: constructorMode } = useSelector((state: RootState) => state.construction); + const dispatch = useDispatch(); + const onToggleMode = (mode: boolean) => dispatch(toggleMode(mode)); + const onSetItems = (items: TBoxType[]) => dispatch(setItems(items)); + + const handleOnDragEnd = (result: DropResult) => { + const { destination, draggableId } = result; + if (!destination) return; + const boxType = draggableId as TBoxType; + if (destination.droppableId === 'calculator' && !items.includes(boxType)) { + const newItems = [...items]; + newItems.splice(destination.index, 0, boxType); + onSetItems(newItems); + } + }; + + return ( +
+ + + {(provided: DroppableProvided) => ( +
+ + {Object.entries(boxes).map(([type, Node], index) => { + const boxType = type as TBoxType; + const inactive = items.includes(boxType); + return ( + + {(draggableProvided: DraggableProvided, snapshot) => ( + +
+ +
+ {snapshot.isDragging && ( + + )} +
+ )} +
+ ); + })} +
+
+ )} +
+
+ + +
+
+
+ ); +} + +export default App; diff --git a/src/components/Button/Button.scss b/src/components/Button/Button.scss new file mode 100644 index 0000000..a498656 --- /dev/null +++ b/src/components/Button/Button.scss @@ -0,0 +1,37 @@ +.button { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 72px; + height: 48px; + background: #ffffff; + border: 1px solid #e2e3e5; + border-radius: 6px; + color: #000; + overflow: hidden; + user-select: none; + + &-display { + background: #f3f4f6; + color: #111827; + text-align: right; + font-weight: 800; + font-size: 36px; + line-height: 44px; + justify-content: flex-end; + flex-direction: row; + } + &-zero { + width: 152px; + } + &-result { + height: 64px; + background: #5d5fef; + color: #fff; + } + &-display, + &-result { + width: 232px; + } +} diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx new file mode 100644 index 0000000..9d2d742 --- /dev/null +++ b/src/components/Button/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import './Button.scss'; +import { combineClassnames } from '../../utils'; + +interface IButtonProps { + label: string; + className?: string; +} + +export const Button: React.FC = ({ label, className }) => { + return
{label}
; +}; diff --git a/src/components/ButtonBox/ButtonBox.scss b/src/components/ButtonBox/ButtonBox.scss new file mode 100644 index 0000000..dfc4f7b --- /dev/null +++ b/src/components/ButtonBox/ButtonBox.scss @@ -0,0 +1,28 @@ +.button-box { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + margin-top: 12px; + padding: 4px; + background: #ffffff; +} + +.box { + &__display { + height: 60px; + } + &__operations .button { + width: 52px; + } + &__digits { + height: 224px; + } + &__result { + height: 72px; + } +} + +.dragging { + opacity: 0.7; +} diff --git a/src/components/ButtonBox/index.tsx b/src/components/ButtonBox/index.tsx new file mode 100644 index 0000000..3999e6b --- /dev/null +++ b/src/components/ButtonBox/index.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import './ButtonBox.scss'; +import { Button } from '../Button'; +import { combineClassnames } from '../../utils'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../store'; +import { operations } from '../../assets/constants'; + +interface IButtonBoxProps { + className?: string; + children: React.ReactNode; +} + +export const ButtonBox: React.FC = ({ className, children }) => { + return
{children}
; +}; + +export const DisplayBox: React.FC = () => { + const { display } = useSelector((state: RootState) => state.calculator); + return ( + +