├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docs ├── FormBuilder-preview-edit.PNG ├── FormBuilder-preview.PNG └── QuizzDevlivery.PNG ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.ts ├── Quiz │ └── Quiz.tsx ├── QuizzBuilder │ ├── FormBuilder.tsx │ ├── FormPreview │ │ ├── DnDWrapper │ │ │ └── index.tsx │ │ ├── ElementWrapper │ │ │ ├── DeleteButton.tsx │ │ │ ├── EditElement │ │ │ │ ├── EditButton.tsx │ │ │ │ ├── SideDrawer │ │ │ │ │ ├── SettingsForm │ │ │ │ │ │ ├── CustomFormInput │ │ │ │ │ │ │ ├── OptionsInput │ │ │ │ │ │ │ │ ├── AddInputOption.tsx │ │ │ │ │ │ │ │ └── OptionsInput.tsx │ │ │ │ │ │ │ └── QuillFormInput │ │ │ │ │ │ │ │ ├── AddDropdown.tsx │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ └── quillFormInput.css │ │ │ │ │ │ └── SettingsForm.tsx │ │ │ │ │ ├── drawer.module.css │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── ElementWrapper.tsx │ │ └── FormPreview.tsx │ ├── ToolBoxContainer.tsx │ └── reducer │ │ ├── actions.ts │ │ └── reducer.ts ├── QuizzContext.tsx ├── ReusableComponents │ ├── BottomButtons │ │ ├── bottomButtons.css │ │ └── index.tsx │ └── TextWithInfo.tsx ├── ToolBox │ ├── Components │ │ ├── Checkboxes.tsx │ │ ├── DatePicker.tsx │ │ ├── Divider.tsx │ │ ├── HeaderText.tsx │ │ ├── Label.tsx │ │ ├── MultiLineInput.tsx │ │ ├── NumberInput.tsx │ │ ├── RadioButtons.tsx │ │ ├── Rate.tsx │ │ ├── Select.tsx │ │ ├── Tags.tsx │ │ └── TextInput.tsx │ ├── CustomIcons │ │ ├── headericon.tsx │ │ ├── inputIcon.tsx │ │ ├── multiLineInputIcon.tsx │ │ ├── numberInputIcon.tsx │ │ └── radiobuttonicon.tsx │ └── index.tsx ├── assets │ ├── FormBuilder.css │ └── antd.css ├── customHooks.ts ├── index.tsx ├── react-app-env.d.ts └── translations │ ├── TranslatedText.tsx │ └── messages │ └── en.json ├── tsconfig.compile.json └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Vendoring antd css file 2 | src/assets/antd.css linguist-vendored=true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /lib 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | package-lock.json 26 | .vscode/ 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | .babelrc 3 | images.d.ts 4 | package-lock.json 5 | tsconfig.prod.json 6 | tsconfig.test.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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-quizzes 2 | 3 | [![MIT License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=plastic)](http://opensource.org/licenses/MIT) 4 | [![npm version](https://img.shields.io/badge/npm-v0.3.1-green.svg?style=plastic)](https://www.npmjs.com/package/react-quizzes) 5 | 6 | Demo: 7 | [![Edit react-quizzesExample](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/magical-chandrasekhar-88del?fontsize=14&hidenavigation=1&theme=dark) 8 | 9 | !["IMG"](./docs/FormBuilder-preview.PNG "example") 10 | 11 | React form builder and form delivery solution for admins and clients that makes forms easy peasy. 12 | Inspired by abandoned project: https://github.com/blackjk3/react-form-builder 13 | 14 | Advantages: 15 | 16 | - Supports custom inputs 17 | - Rich text questions 18 | - Supports custom styles 19 | - Internationalization ISO'S 20 | - Internationalization on questions and answers 21 | - Internationalization on Builder 22 | - Centralized form builder and from delivery 23 | - Drag & Drop to order/sort questions on `` 24 | 25 | ## Installation 26 | 27 | ### Install via NPM 28 | 29 | ```bash 30 | npm install --save react-quizzes 31 | ``` 32 | 33 | ## Style 34 | 35 | For components design we use, antd-design componentes and if you just need som simple things we provide the antd style just import it like this: 36 | `import "react-quizzes/lib/assets/antd.css"` 37 | For custom styling check [`Custom styles`](#Custom-styles) section 38 | 39 | `react-quizzes` requires `react` and `react-dom` as [`peerDependency`](https://docs.npmjs.com/files/package.json#peerdependencies) 40 | 41 | # QuizzBuilder 42 | 43 | ```html 44 | import { QuizzBuilder } from "react-quizzes" console.log(form)} /> 46 | ``` 47 | 48 | ## API 49 | 50 | QuizzBuilder component objective is to provide the user a nice and smooth interface to build quizzes 51 | 52 | | Props | Type | Default | Description | 53 | | -------------- | --------------- | ----------------------- | -------------------------------------------------------------------------------- | 54 | | `onChange` | `Function` | `` | will returns builded quizz in QuizzData type | 55 | | `initialValue` | `QuizzData` | `` | initial value to QuizzBuilder, useful if user wants to edit a saved quizz | 56 | | `toolBox` | `QuizzToolBox` | `default QuizzToolBox` | list of inputs to use, defaults to react-quizz but custom inputs can be supplied | 57 | | `language` | `string` | `en` | Language that QuizzBuilder will show | 58 | | `messages` | `QuizzMessages` | `default QuizzMessages` | Object with each language and each language with each text translation | 59 | 60 | # Quiz 61 | 62 | A component that provides the final user a quiz/form to fill 63 | 64 | ```html 65 | import { Quiz } from "react-quizzes" 66 | console.log("form submited values", values)} /> 67 | ``` 68 | 69 | ## API 70 | 71 | | Props | Type | Default | Description | 72 | | -------------- | --------------- | ----------------------- | -------------------------------------------------------------------------------- | 73 | | `data` | `QuizzData` | `` | data to build the final user form to be filled | 74 | | `onSubmit` | `Fucntion` | `` | returns the submitted form values | 75 | | `submitButton` | `boolean` | `true` | shows/hides default submit button\* | 76 | | `toolBox` | `QuizzToolBox` | `default QuizzToolBox` | list of inputs to use, defaults to react-quizz but custom inputs can be supplied | 77 | | `language` | `string` | `en` | Language that Quiz questions and options will show | 78 | | `messages` | `QuizzMessages` | `default QuizzMessages` | Object with each language and each language with each text translation | 79 | 80 | - if submit button is hidden the default onSubmit will not work, you must implement a custom submit 81 | 82 | ## Custom submit 83 | 84 | There is a prop `wrappedComponentRef` that gives you access to make basically anything, reset form, set initial values change the values based on something.... 85 | 86 | [`wrappedComponentRef`](https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140) 87 | 88 | ```jsx 89 | import { Quiz } from "react-quizzes"; 90 | 91 | saveQuizRef = (quizRef) => { 92 | // saves Quizz component ref 93 | this.quizRef = quizRef; 94 | }; 95 | // custom submit function 96 | handleCustomSubmit = () => { 97 | const form = this.quizRef.props.form; 98 | form.validateFields((err, values) => { 99 | if (!err) { 100 | // if no errors, no errors means required answers are filled 101 | console.log("Received values of form: ", values); 102 | form.resetFields(); // resets form after recieveing values 103 | } 104 | }); 105 | }; 106 | 107 | 112 | 113 | ; 114 | ``` 115 | 116 | # Translations/Internationalization 117 | 118 | New languages support can be added or replace the existing ones 119 | [Existing translations](./src/translations/TranslatedText.tsx#defaultMessages) 120 | 121 | [![Edit react-quizzesExample RU locale](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-quizzesexample-ru-locale-wx50m?fontsize=14&hidenavigation=1&theme=dark) 122 | 123 | ```javascript 124 | import { QuizzBuilder } from "react-quizzes" 125 | import { defaultMessages } from "react-quizzes/lib/translations/TranslatedText"; 126 | 127 | // existing keys can be found on above link 128 | defaultMessages["pt"]={ 129 | "toolbox.textinput.name": "Caixa de Texto", 130 | "confirm.action": "Tem a certeza?", 131 | "btn.yes": "Sim", 132 | "btn.no": "Não", 133 | "btn.add": "Adicionar", 134 | ... 135 | } 136 | function App() { 137 | return console.log(form)} 139 | language="pt" 140 | messages={toolBoxItems} 141 | /> 142 | } 143 | ``` 144 | 145 | # Custom Inputs 146 | 147 | Custom inputs can be added to Toolbox, but Toolbox already supplies a great variety of inputs, if you have a new input suggestions fell free to contact this library contributors. 148 | 149 | The Toolbox is used on QuizzBuilder and Quiz, so if you created a form with custom input on QuizzBuilder supply the same toolbox to Quiz component so it can display the input 150 | 151 | [Existing Toolbox](./src/ToolBox/index.tsx) 152 | 153 | ## How to add custom input 154 | 155 | You just have to pass the prop `toolBox` and add your new entry to the existing toolbox items, or if you don't want to reuse the existing one just make your one 156 | 157 | ```javascript 158 | import { QuizzBuilder } from "react-quizzes" 159 | import defaulttoolBox from "react-quizzes/ToolBox" 160 | import { Avatar } from "antd"; 161 | // existing keys can be found on above link 162 | cosnt toolbox=defaulttoolBox() 163 | toolbox.push( 164 | { 165 | key: "MyInput_", 166 | name: "toolbox.input.name", // id of translation 167 | questions: { 168 | "en": "How are you ?" 169 | ... 170 | }, 171 | // description: "toolbox.headertext.description", // desciption under input on toolbox 172 | icon: , // this will go to Dom so can be string|| jsx component 173 | field_name: "MyInput_", // will add a generated uuidv4 174 | Component: MyInput // component not instanciated 175 | } 176 | ) 177 | 178 | function App() { 179 | return console.log(form)} 181 | toolBox={toolbox} 182 | /> 183 | } 184 | ``` 185 | 186 | [MyInput example](./src/ToolBox/Components/TextInput.tsx) 187 | 188 | Example: 189 | [URL TO FILE] 190 | 191 | # Custom styles 192 | 193 | Default style is supplied `import "react-quizzes/lib/assets/antd.css"` 194 | 195 | but if you need a custom one follow antd-design guidelines and probably you will just want to use less to make it easier 196 | 197 | [Antd customize theme](https://ant.design/docs/react/customize-theme) 198 | 199 | # License 200 | 201 | MIT License 202 | 203 | # DEV 204 | 205 | Start project 206 | 207 | ``` 208 | npm i 209 | npm start 210 | ``` 211 | 212 | # Compile lib for npm 213 | 214 | Compile project 215 | 216 | ``` 217 | npm i 218 | npm run compile 219 | ``` 220 | 221 | # Deply lib for Github Pages 222 | 223 | Start project 224 | 225 | ``` 226 | npm i 227 | npm run deploy 228 | ``` 229 | -------------------------------------------------------------------------------- /docs/FormBuilder-preview-edit.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobarragon/react-quizzes/085a3963e3bdc7f70842ab53a690fa01a0adca4f/docs/FormBuilder-preview-edit.PNG -------------------------------------------------------------------------------- /docs/FormBuilder-preview.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobarragon/react-quizzes/085a3963e3bdc7f70842ab53a690fa01a0adca4f/docs/FormBuilder-preview.PNG -------------------------------------------------------------------------------- /docs/QuizzDevlivery.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobarragon/react-quizzes/085a3963e3bdc7f70842ab53a690fa01a0adca4f/docs/QuizzDevlivery.PNG -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-quizzes", 3 | "version": "0.3.1", 4 | "private": false, 5 | "description": "React quizz/form builder and delivery solution", 6 | "main": "lib/App.js", 7 | "types": "lib", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/hugobarragon/react-quizzes.git" 11 | }, 12 | "keywords": [ 13 | "react-form-builder", 14 | "react form builder", 15 | "react-quizzes", 16 | "antd form builder", 17 | "surveyjs", 18 | "form builder" 19 | ], 20 | "author": "Hugo Barragon (http://github.com/hugobarragon)", 21 | "contributors": [], 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/hugobarragon/react-quizzes/issues" 25 | }, 26 | "homepage": "https://hugobarragon.github.io/react-quizzes/", 27 | "dependencies": { 28 | "@types/lodash.clonedeep": "^4.5.6", 29 | "@types/lodash.isequal": "^4.5.5", 30 | "@types/node": "12.12.3", 31 | "@types/uuid": "^3.4.9", 32 | "antd": "^3.26.20", 33 | "iso-639-1": "^2.1.9", 34 | "lodash.clonedeep": "^4.5.0", 35 | "lodash.isequal": "^4.5.0", 36 | "react-dnd": "^11.1.3", 37 | "react-dnd-html5-backend": "^11.1.3", 38 | "react-quill": "^1.3.5", 39 | "uuid": "^3.4.0" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.13.0", 43 | "@types/jest": "^24.9.1", 44 | "@types/react": "^16.14.8", 45 | "@types/react-dom": "^16.9.13", 46 | "copyfiles": "^2.4.1", 47 | "gh-pages": "^3.2.1", 48 | "react": "^16.14.0", 49 | "react-dom": "^16.14.0", 50 | "react-scripts": "^3.4.4", 51 | "serve": "^12.0.0", 52 | "typescript": "^3.9.9" 53 | }, 54 | "peerDependencies": { 55 | "react": ">=16.8", 56 | "react-dom": ">=16.8" 57 | }, 58 | "scripts": { 59 | "start": "react-scripts start", 60 | "build": "react-scripts build", 61 | "compile": "tsc -p ./tsconfig.compile.json", 62 | "postcompile": "copyfiles -u 1 src/**/*.css lib/", 63 | "predeploy": "npm run build", 64 | "deploy": "gh-pages -d build", 65 | "test": "react-scripts test", 66 | "eject": "react-scripts eject" 67 | }, 68 | "eslintConfig": { 69 | "extends": "react-app" 70 | }, 71 | "browserslist": { 72 | "production": [ 73 | ">0.2%", 74 | "not dead", 75 | "not op_mini all" 76 | ], 77 | "development": [ 78 | "last 1 chrome version", 79 | "last 1 firefox version", 80 | "last 1 safari version" 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobarragon/react-quizzes/085a3963e3bdc7f70842ab53a690fa01a0adca4f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | react-quizzes 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobarragon/react-quizzes/085a3963e3bdc7f70842ab53a690fa01a0adca4f/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugobarragon/react-quizzes/085a3963e3bdc7f70842ab53a690fa01a0adca4f/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.ts: -------------------------------------------------------------------------------- 1 | export { default as QuizzBuilder } from "./QuizzBuilder/FormBuilder"; 2 | export { default as Quiz } from "./Quiz/Quiz"; 3 | -------------------------------------------------------------------------------- /src/Quiz/Quiz.tsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from "react"; 2 | import QuizzContext, { 3 | getDefaultContext, 4 | IQuizzContext 5 | } from "../QuizzContext"; 6 | import TranslatedText from "../translations/TranslatedText"; 7 | import { Empty, Button, Form } from "antd"; 8 | 9 | class Quizz extends PureComponent { 10 | // submit handler 11 | handleSubmit(e: React.MouseEvent) { 12 | e.preventDefault(); 13 | const { onSubmit, form } = this.props; 14 | 15 | form.validateFields((err: any, values: any) => { 16 | if (!err) { 17 | if (typeof onSubmit === "function") { 18 | // sends values to parent if has onSubmit 19 | onSubmit(values); 20 | } 21 | } 22 | }); 23 | } 24 | 25 | static defaultProps = { 26 | submitButton: true 27 | }; 28 | 29 | render() { 30 | const { form, data, submitButton, ...rest } = this.props, 31 | contextValue = getDefaultContext(rest as IQuizzContext); 32 | return ( 33 |
34 | 35 |
36 | {data.map((item: any) => { 37 | const current_key = item.element; 38 | // searchs for the current input on the toolbox to get the component 39 | const found_toolbox_input = contextValue["toolBox"].find( 40 | (toolBoxInput: any) => current_key === toolBoxInput.key 41 | ) as any; 42 | // finds input and wrapps it with delete and edit button 43 | const Component = found_toolbox_input 44 | ? found_toolbox_input.Component 45 | : Empty; 46 | 47 | return ( 48 | 56 | ); 57 | })} 58 | {submitButton ? ( 59 | 60 | 67 | 68 | ) : null} 69 | 70 |
71 |
72 | ); 73 | } 74 | } 75 | 76 | export default Form.create()(Quizz); 77 | -------------------------------------------------------------------------------- /src/QuizzBuilder/FormBuilder.tsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, memo, useEffect } from "react"; 2 | import { reducer, initialState } from "./reducer/reducer"; 3 | import Row from "antd/es/row/index"; 4 | import Col from "antd/es/col/index"; 5 | import ToolBox from "./ToolBoxContainer"; 6 | import FormPreview from "./FormPreview/FormPreview"; 7 | import QuizzContext, { getDefaultContext } from "../QuizzContext"; 8 | import { usePrevious } from "../customHooks"; 9 | import isEqual from "lodash.isequal"; 10 | import "../assets/FormBuilder.css"; 11 | 12 | export default memo(function(props: any) { 13 | const { onChange, initialValue, ...rest } = props, 14 | [state, dispatch] = useReducer(reducer, initialState(initialValue)), 15 | contextValue = { 16 | ...getDefaultContext(rest), 17 | state, 18 | dispatch 19 | }, 20 | formData = state.get("data"); 21 | const previousFormData = usePrevious(formData); 22 | 23 | // effect to set 24 | useEffect(() => { 25 | // effect to notify parent that form data had a diff 26 | if ( 27 | typeof onChange === "function" && 28 | previousFormData && 29 | !isEqual(previousFormData, formData) 30 | ) { 31 | onChange(formData); 32 | } 33 | }, [onChange, formData, previousFormData]); 34 | 35 | // creates two colons 1 with toolbox and the other with the added inputs 36 | return ( 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /src/QuizzBuilder/FormPreview/DnDWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren, useRef } from "react"; 2 | import { useDrag, useDrop, DropTargetMonitor } from "react-dnd"; 3 | import { XYCoord } from "dnd-core"; 4 | 5 | const ItemTypes = { 6 | CARD: "card", 7 | }; 8 | 9 | export interface CardProps { 10 | id: string; 11 | index: number; 12 | moveCard: (dragIndex: number, hoverIndex: number) => void; 13 | } 14 | 15 | interface DragItem { 16 | index: number; 17 | id: string; 18 | type: string; 19 | } 20 | 21 | export default (props: PropsWithChildren) => { 22 | const { id, index, moveCard, children } = props; 23 | const ref = useRef(null); 24 | const [, drop] = useDrop({ 25 | accept: ItemTypes.CARD, 26 | hover(item: DragItem, monitor: DropTargetMonitor) { 27 | if (!ref.current) { 28 | return; 29 | } 30 | const dragIndex = item.index; 31 | const hoverIndex = index; 32 | 33 | // Don't replace items with themselves 34 | if (dragIndex === hoverIndex) { 35 | return; 36 | } 37 | 38 | // Determine rectangle on screen 39 | const hoverBoundingRect = ref.current?.getBoundingClientRect(); 40 | 41 | // Get vertical middle 42 | const hoverMiddleY = 43 | (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; 44 | 45 | // Determine mouse position 46 | const clientOffset = monitor.getClientOffset(); 47 | 48 | // Get pixels to the top 49 | const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; 50 | 51 | // Only perform the move when the mouse has crossed half of the items height 52 | // When dragging downwards, only move when the cursor is below 50% 53 | // When dragging upwards, only move when the cursor is above 50% 54 | 55 | // Dragging downwards 56 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { 57 | return; 58 | } 59 | 60 | // Dragging upwards 61 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { 62 | return; 63 | } 64 | 65 | // Time to actually perform the action 66 | moveCard(dragIndex, hoverIndex); 67 | 68 | // Note: we're mutating the monitor item here! 69 | // Generally it's better to avoid mutations, 70 | // but it's good here for the sake of performance 71 | // to avoid expensive index searches. 72 | item.index = hoverIndex; 73 | }, 74 | }); 75 | 76 | const [{ isDragging }, drag] = useDrag({ 77 | item: { type: ItemTypes.CARD, id, index }, 78 | collect: (monitor: any) => ({ 79 | isDragging: monitor.isDragging(), 80 | }), 81 | }); 82 | 83 | const opacity = isDragging ? 0 : 1; 84 | drag(drop(ref)); 85 | return ( 86 |
87 | {children} 88 |
89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /src/QuizzBuilder/FormPreview/ElementWrapper/DeleteButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import Button from "antd/es/button/index"; 3 | import Popconfirm from "antd/es/popconfirm/index"; 4 | import { deleteElement } from "../../reducer/actions"; 5 | import QuizzContext, { IQuizzBuilderContext } from "../../../QuizzContext"; 6 | import TranslatedText from "../../../translations/TranslatedText"; 7 | 8 | // applies delete and edit capabilities 9 | export default (props: any) => { 10 | const { dispatch } = useContext(QuizzContext) as IQuizzBuilderContext; 11 | 12 | return ( 13 | dispatch(deleteElement(props.id))} 17 | title={} 18 | okText={} 19 | cancelText={} 20 | > 21 | 73 | 80 | 81 | {languagesList.map((language: string, i: number) => { 82 | return ( 83 | 84 | 85 | {getNativeName(language)} 86 | 87 | onChangeInput(e, language)} /> 88 | 89 | ); 90 | })} 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | }; 100 | -------------------------------------------------------------------------------- /src/QuizzBuilder/FormPreview/ElementWrapper/EditElement/SideDrawer/SettingsForm/CustomFormInput/OptionsInput/OptionsInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | import Tabs from "antd/es/tabs/index"; 3 | import Row from "antd/es/row/index"; 4 | import Col from "antd/es/col/index"; 5 | import Input from "antd/es/input/Input"; 6 | import Button from "antd/es/button/button"; 7 | import cloneDeep from "lodash.clonedeep"; 8 | import TextWithInfo from "../../../../../../../../ReusableComponents/TextWithInfo"; 9 | import AddInputOption from "./AddInputOption"; 10 | 11 | const { TabPane } = Tabs; 12 | 13 | export default forwardRef((props: any, ref) => { 14 | const { 15 | value, 16 | onChange, 17 | currentLanguage, 18 | setLanguage, 19 | languagesList 20 | } = props; 21 | 22 | // handles on change ofr text and value 23 | function onChangeInput(index: number, content: string, language?: string) { 24 | const valueClone = cloneDeep(value); 25 | if (language) { 26 | // language exists means its text editing 27 | valueClone[index]["text"][language] = content; 28 | } else { 29 | // value editing 30 | valueClone[index]["value"] = content; 31 | } 32 | 33 | onChange(valueClone); 34 | } 35 | 36 | // handles delete option 37 | function onDelete(index: number) { 38 | const valueClone = cloneDeep(value); 39 | // removes provided index 40 | valueClone.splice(index, 1); 41 | 42 | onChange(valueClone); 43 | } 44 | 45 | // on add new option 46 | function onAddOption(newOption: object) { 47 | const valueClone = cloneDeep(value); 48 | valueClone.push(newOption); 49 | 50 | onChange(valueClone); 51 | } 52 | 53 | return ( 54 | 55 | 56 | 61 | 62 | 63 | 64 | 67 |

Each option can be translated

68 |

Current Language: {currentLanguage}

69 | 70 | } 71 | > 72 | Text: 73 |
74 | {/* Text options */} 75 |
} 79 | > 80 | {languagesList.map((language: string) => ( 81 | 82 | {value.map((option: any, i: number) => ( 83 | onChangeInput(i, e.target.value, language)} 87 | /> 88 | ))} 89 | 90 | ))} 91 | 92 | 93 | 94 | 95 | Value: 96 | 97 | 98 | {/* VALUES ARE THE SAME BETWEEN LANGUAGES */} 99 | {value.map((option: any, i: number) => ( 100 | 101 | 102 | onChangeInput(i, e.target.value)} 105 | /> 106 | 107 | 108 | {i > 0 ? ( 109 | /* only show delete button if not first option */ 110 |