├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .storybook ├── addons.js ├── config.js └── webpack.config.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── docs ├── COMMIT_MESSAGE_CONVENTION.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── package-lock.json ├── package.json ├── src └── index.js ├── stories ├── dummy-data.js └── index.stories.js └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | parserOptions: { 4 | ecmaVersion: 7, 5 | sourceType: 'module', 6 | ecmaFeatures: { 7 | jsx: true 8 | } 9 | }, 10 | extends: ['tui/es6', 'plugin:react/recommended', 'plugin:prettier/recommended'], 11 | plugins: ['react', 'prettier'], 12 | rules: { 13 | 'react/prop-types': 0 14 | }, 15 | settings: { 16 | react: { 17 | version: 'detect' 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | Thumbs.db 4 | Desktop.ini 5 | dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth" : 100, 3 | "singleQuote" : true 4 | } -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register'; 2 | import '@storybook/addon-actions/register'; 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | // automatically import all files ending in *.stories.js 4 | const req = require.context('../stories', true, /.stories.js$/); 5 | function loadStories() { 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | // you can use this file to add your custom webpack plugins, loaders and anything you like. 2 | // This is just the basic way to add additional webpack configurations. 3 | // For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config 4 | 5 | // IMPORTANT 6 | // When you add this file, we won't add the default configurations which is similar 7 | // to "React Create App". This only has babel loader to load JavaScript. 8 | 9 | module.exports = { 10 | plugins: [ 11 | // your custom plugins 12 | ], 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.css$/, 17 | use: ['style-loader', 'css-loader'] 18 | } 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | Our Pledge 3 | 4 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 5 | Our Standards 6 | 7 | Examples of behavior that contributes to creating a positive environment include: 8 | 9 | Using welcoming and inclusive language 10 | Being respectful of differing viewpoints and experiences 11 | Gracefully accepting constructive criticism 12 | Focusing on what is best for the community 13 | Showing empathy towards other community members 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | The use of sexualized language or imagery and unwelcome sexual attention or advances 18 | Trolling, insulting/derogatory comments, and personal or political attacks 19 | Public or private harassment 20 | Publishing others' private information, such as a physical or electronic address, without explicit permission 21 | Other conduct which could reasonably be considered inappropriate in a professional setting 22 | 23 | Our Responsibilities 24 | 25 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 26 | 27 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 28 | Scope 29 | 30 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 31 | Enforcement 32 | 33 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dl_javascript@nhn.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 34 | 35 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 36 | Attribution 37 | 38 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to TOAST UI 2 | 3 | First off, thanks for taking the time to contribute! 🎉 😘 ✨ 4 | 5 | The following is a set of guidelines for contributing to TOAST UI. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | Reporting Bugs 7 | 8 | Bugs are tracked as GitHub issues. Search the list and try reproduce on demo before you create an issue. When you create an issue, please provide the following information by filling in the template. 9 | 10 | Explain the problem and include additional details to help maintainers reproduce the problem: 11 | 12 | Use a clear and descriptive title for the issue to identify the problem. 13 | Describe the exact steps which reproduce the problem in as many details as possible. Don't just say what you did, but explain how you did it. For example, if you moved the cursor to the end of a line, explain if you used a mouse or a keyboard. 14 | Provide specific examples to demonstrate the steps. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets on the issue, use Markdown code blocks. 15 | Describe the behavior you observed after following the steps and point out what exactly is the problem with that behavior. 16 | Explain which behavior you expected to see instead and why. 17 | Include screenshots and animated GIFs which show you following the described steps and clearly demonstrate the problem. 18 | 19 | Suggesting Enhancements 20 | 21 | In case you want to suggest for TOAST UI Editor, please follow this guideline to help maintainers and the community understand your suggestion. Before creating suggestions, please check issue list if there's already a request. 22 | 23 | Create an issue and provide the following information: 24 | 25 | Use a clear and descriptive title for the issue to identify the suggestion. 26 | Provide a step-by-step description of the suggested enhancement in as many details as possible. 27 | Provide specific examples to demonstrate the steps. Include copy/pasteable snippets which you use in those examples, as Markdown code blocks. 28 | Include screenshots and animated GIFs which helps demonstrate the steps or point out the part of TOAST UI Editor which the suggestion is related to. 29 | Explain why this enhancement would be useful to most TOAST UI users. 30 | List some other text editors or applications where this enhancement exists. 31 | 32 | First Code Contribution 33 | 34 | Unsure where to begin contributing to TOAST UI? You can start by looking through these document, good first issue and help wanted issues: 35 | 36 | document issues: issues which should be reviewed or improved. 37 | good first issues: issues which should only require a few lines of code, and a test or two. 38 | help wanted issues: issues which should be a bit more involved than beginner issues. 39 | 40 | Pull Requests 41 | Development WorkFlow 42 | 43 | Set up your development environment 44 | Make change from a right branch 45 | Be sure the code passes npm run lint 46 | Make a pull request 47 | 48 | Development environment 49 | 50 | Prepare your machine node and it's packages installed. 51 | Checkout our repository 52 | Install dependencies by npm install 53 | 54 | Make changes 55 | Checkout a branch 56 | 57 | master: PR Base branch. 58 | production: lastest release branch with distribution files. never make a PR on this 59 | gh-pages: API docs, examples and demo 60 | 61 | Check Code Style 62 | 63 | Run npm run lint and make sure all the tests pass. 64 | Commit 65 | 66 | Follow our commit message conventions. 67 | Yes! Pull request 68 | 69 | Make your pull request, then describe your changes. 70 | Title 71 | 72 | Follow other PR title format on below. 73 | 74 | : Short Description (fix #111) 75 | : Short Description (fix #123, #111, #122) 76 | : Short Description (ref #111) 77 | 78 | capitalize first letter of Type 79 | use present tense: 'change' not 'changed' or 'changes' 80 | 81 | Description 82 | 83 | If it has related to issues, add links to the issues(like #123) in the description. Fill in the Pull Request Template by check your case. 84 | Code of Conduct 85 | 86 | This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to dl_javascript@nhn.com. 87 | 88 | This Guide is base on atom contributing guide, CocoaPods and ESLint 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 NHN Corp. 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️Notice: This repository is deprecated️️️️️ 2 | 3 | TOAST UI Grid React Wrapper has been managed separately from the TOAST UI Grid repository. As a result of the distribution of these issues, we decided to deprecated each wrapper repository and manage repository as a [mono-repo](https://en.wikipedia.org/wiki/Monorepo) from the [TOAST UI Grid repository](https://github.com/nhn/tui.grid). 4 | 5 | From now on, please submit issues or contributings related to TOAST UI React Wrapper to [TOAST UI Grid repository](https://github.com/nhn/tui.grid). Thank you🙂. 6 | 7 | # TOAST UI Grid for React 8 | 9 | > This is a React component wrapping [TOAST UI Grid](https://github.com/nhn/tui.grid). 10 | 11 | [![github version](https://img.shields.io/github/release/nhn/toast-ui.react-grid.svg)](https://github.com/nhn/toast-ui.react-grid/releases/latest) 12 | [![npm version](https://img.shields.io/npm/v/@toast-ui/react-grid.svg)](https://www.npmjs.com/package/@toast-ui/react-grid) 13 | [![license](https://img.shields.io/github/license/nhn/toast-ui.react-grid.svg)](https://github.com/nhn/toast-ui.react-grid/blob/master/LICENSE) 14 | [![PRs welcome](https://img.shields.io/badge/PRs-welcome-ff69b4.svg)](https://github.com/nhn/toast-ui.react-grid/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) 15 | [![code with hearth by NHN](https://img.shields.io/badge/%3C%2F%3E%20with%20%E2%99%A5%20by-NHN-ff1414.svg)](https://github.com/nhn) 16 | 17 | ## 🚩 Table of Contents 18 | * [Collect statistics on the use of open source](#collect-statistics-on-the-use-of-open-source) 19 | * [Install](#-install) 20 | * [Using npm](#using-npm) 21 | * [Usage](#-usage) 22 | * [Import](#import) 23 | * [Props](#props) 24 | * [Reactive props](#reactive-props) 25 | * [Instance Methods](#instance-methods) 26 | * [Getting the root element](#getting-the-root-element) 27 | * [Static Methods](#static-methods) 28 | * [Events](#events) 29 | * [Addons](#addons) 30 | * [Pull Request Steps](#-pull-request-steps) 31 | * [Documents](#-documents) 32 | * [Contributing](#-contributing) 33 | * [License](#-license) 34 | 35 | ## Collect statistics on the use of open source 36 | 37 | React Wrapper of TOAST UI Grid applies Google Analytics (GA) to collect statistics on the use of open source, in order to identify how widely TOAST UI Grid is used throughout the world. It also serves as important index to determine the future course of projects. location.hostname (e.g. > “ui.toast.com") is to be collected and the sole purpose is nothing but to measure statistics on the usage. To disable GA, use the `usageStatistics` props like the example below. 38 | 39 | ```js 40 | 45 | ``` 46 | 47 | Or, import `tui-code-snippet.js` (**v1.4.0** or **later**) and then immediately write the options as follows: 48 | ```js 49 | tui.usageStatistics = false; 50 | ``` 51 | 52 | ## 💾 Install 53 | 54 | ### Using npm 55 | 56 | ```sh 57 | npm install --save @toast-ui/react-grid 58 | ``` 59 | 60 | ## 🔡 Usage 61 | 62 | ### Import 63 | 64 | You can use Toast UI Grid for React as a ECMAScript module or a CommonJS module. As this module does not contain CSS files, you should import `tui-grid.css` from `tui-grid` manually. 65 | 66 | * Using ECMAScript module 67 | 68 | ```js 69 | import 'tui-grid/dist/tui-grid.css' 70 | import Grid from '@toast-ui/react-grid' 71 | ``` 72 | 73 | * Using CommonJS module 74 | 75 | ```js 76 | require('tui-grid/dist/tui-grid.css'); 77 | const Grid = require('@toast-ui/react-grid'); 78 | ``` 79 | 80 | ### Props 81 | 82 | [All the options of the TOAST UI Grid](http://nhn.github.io/tui.grid/latest/Grid) are supported in the form of props. Note that `data` and `columns` props are required and other props are optional. 83 | 84 | ```js 85 | const data = [ 86 | {id: 1, name: 'Editor'}, 87 | {id: 2, name: 'Grid'} 88 | {id: 3, name: 'Chart'} 89 | ]; 90 | 91 | const columns = [ 92 | {name: 'id', header: 'ID'}, 93 | {name: 'name', header: 'Name'} 94 | ]; 95 | 96 | const MyComponent = () => ( 97 | 106 | 107 | ); 108 | ``` 109 | 110 | ### Reactive Props 111 | 112 | Normally, React components are re-rendered whenever the props received from a parent component are changed. But not all the props of the wrapper component are reactive as the TOAST UI Grid does not provide setter methods for all options. Below are the list of reactive props which are currently supported. 113 | 114 | - `data` (using `setData`) 115 | - `columns` (using `setColumns`) 116 | - `bodyHeight` (using `setBodyHeight`) 117 | - `frozenColumnCount` (using `setFrozenColumnCount`) 118 | 119 | If you don't want some props to be reactive, you can disable reactivity of specific props using `oneTimeBindingProps`. For example, if you don't want to re-render whenever `data` and `columns` props are changed, you can use `oneTimeBindingProps` like the example below. 120 | 121 | ```js 122 | const MyComponent = () => ( 123 | 130 | ); 131 | ``` 132 | 133 | ### Instance Methods 134 | 135 | For using [instance methods of TOAST UI Grid](http://nhn.github.io/tui.grid/latest/Grid#activateFocus), first thing to do is creating Refs of wrapper component using [`createRef()`](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs). But the wrapper component does not provide a way to call instance methods of TOAST UI Grid directly. Instead, you can call `getInstance()` method of the wrapper component to get the instance, and call the methods on it. 136 | 137 | ```js 138 | class MyComponent extends React.Component { 139 | gridRef = React.createRef(); 140 | 141 | handleAppendRow = () => { 142 | this.gridRef.current.getInstance().appendRow({}); 143 | } 144 | 145 | render() { 146 | return ( 147 | <> 148 | 153 | 154 | 155 | ); 156 | } 157 | } 158 | ``` 159 | 160 | ### Getting the root element 161 | 162 | An instance of the wrapper component also provides a handy method for getting the root element. If you want to manipulate the root element directly, you can call `getRootElement` to get the element. 163 | 164 | ```js 165 | class MyComponent extends React.Component { 166 | gridRef = React.createRef(); 167 | 168 | handleClickButton = () => { 169 | this.gridRef.current.getRootElement().classList.add('my-grid-root'); 170 | } 171 | 172 | render() { 173 | return ( 174 | <> 175 | 180 | 181 | 182 | ); 183 | } 184 | } 185 | ``` 186 | 187 | ### Static Methods 188 | 189 | The wrapper component does not provide a way to call [static methods of TOAST UI Grid](http://nhn.github.io/tui.grid/latest/Grid#applyTheme). If you want to use static methods such as `applyTheme` or `setLanguage` you should use it via importing `tui-grid` directly. 190 | 191 | ```js 192 | import TuiGrid from 'tui-grid'; 193 | 194 | TuiGrid.setLanguage('ko'); 195 | TuiGrid.applyTheme('striped'); 196 | ``` 197 | 198 | ### Events 199 | [All the events of TOAST UI Grid](http://nhn.github.io/tui.grid/latest/Grid#event-beforeRequest) are supported in the form of `on[EventName]` props. The first letter of each event name should be capitalized. For example, for using `click` event you can use `onClick` prop like the example below. 200 | 201 | ```js 202 | class MyComponent extends React.Component { 203 | handleClick = () => { 204 | console.log('click!!'); 205 | } 206 | 207 | render() { 208 | return ( 209 | 214 | ); 215 | } 216 | } 217 | ``` 218 | 219 | ### DataSource 220 | In general, the TOAST UI Grid runs on the front-end environment using local data. However, you can also bind remote data using a plain object called `dataSource`. To use this, define the `dataSource` object and set it to the data option like the example below. 221 | 222 | ```js 223 | const columns = [/* ... */]; 224 | const dataSource = { 225 | withCredentials: false, 226 | initialRequest: true, 227 | api: { 228 | readData: {url: 'api/readData', method: 'GET'} 229 | } 230 | }; 231 | 232 | const MyComponent = () => ( 233 | { 238 | console.log(data); 239 | }} 240 | /> 241 | ); 242 | ``` 243 | 244 | ### With React Hooks 245 | 246 | React Hooks can be used together. 247 | 248 | ```js 249 | import React, {useCallback} from 'react'; 250 | 251 | const MyComponentWithHooks = () => { 252 | const onClick = useCallback(() => { 253 | console.log('condition:', condition); 254 | }, [condition]); 255 | 256 | return ; 257 | }; 258 | ``` 259 | 260 | 261 | ## 🔧 Pull Request Steps 262 | 263 | TOAST UI products are open source, so you can create a pull request(PR) after you fix issues. 264 | Run npm scripts and develop yourself with the following process. 265 | 266 | ### Setup 267 | 268 | Fork `master` branch into your personal repository. 269 | Clone it to local computer. Install node modules. 270 | Before starting development, you should check to have any errors. 271 | 272 | ``` sh 273 | $ git clone https://github.com/{your-personal-repo}/[[repo name]].git 274 | $ cd [[repo name]] 275 | $ npm install 276 | ``` 277 | 278 | ### Develop 279 | 280 | Let's start development! 281 | 282 | ### Pull Request 283 | 284 | Before PR, check to test lastly and then check any errors. 285 | If it has no error, commit and then push it! 286 | 287 | For more information on PR's step, please see links of Contributing section. 288 | 289 | ## 💬 Contributing 290 | * [Code of Conduct](https://github.com/nhn/toast-ui.react-grid/blob/master/CODE_OF_CONDUCT.md) 291 | * [Contributing guideline](https://github.com/nhn/toast-ui.react-grid/blob/master/CONTRIBUTING.md) 292 | * [Commit convention](https://github.com/nhn/toast-ui.react-grid/blob/master/docs/COMMIT_MESSAGE_CONVENTION.md) 293 | 294 | ## 📜 License 295 | This software is licensed under the [MIT](./LICENSE) © [NHN.](https://github.com/nhn) 296 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | api.cache(true); 3 | 4 | return { 5 | presets: [ 6 | [ 7 | '@babel/preset-env', 8 | { 9 | modules: false, 10 | useBuiltIns: 'entry' 11 | } 12 | ], 13 | '@babel/preset-react' 14 | ], 15 | plugins: [ 16 | '@babel/plugin-proposal-class-properties', 17 | '@babel/plugin-proposal-object-rest-spread' 18 | ] 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /docs/COMMIT_MESSAGE_CONVENTION.md: -------------------------------------------------------------------------------- 1 | # Commit Message Convention 2 | 3 | ## Commit Message Format 4 | 5 | ``` 6 | : Short description (fix #1234) 7 | 8 | Logger description here if necessary 9 | 10 | BREAKING CHANGE: only contain breaking change 11 | ``` 12 | * Any line of the commit message cannot be longer 100 characters! 13 | 14 | ## Revert 15 | ``` 16 | revert: commit 17 | 18 | This reverts commit 19 | More description if needed 20 | ``` 21 | 22 | ## Type 23 | Must be one of the following: 24 | 25 | * **feat**: A new feature 26 | * **fix**: A bug fix 27 | * **docs**: Documentation only changes 28 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 29 | * **refactor**: A code change that neither fixes a bug nor adds a feature 30 | * **perf**: A code change that improves performance 31 | * **test**: Adding missing or correcting existing tests 32 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation 33 | 34 | ## Subject 35 | * use the imperative, __present__ tense: "change" not "changed" nor "changes" 36 | * don't capitalize the first letter 37 | * no dot (.) at the end 38 | * reference GitHub issues at the end. If the commit doesn’t completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`. 39 | 40 | ## Body 41 | 42 | * use the imperative, __present__ tense: "change" not "changed" nor "changes". 43 | * the motivation for the change and contrast this with previous behavior. 44 | 45 | ## BREAKING CHANGE 46 | * This commit contains breaking change(s). 47 | * start with the word BREAKING CHANGE: with a space or two newlines. The rest of the commit message is then used for this. 48 | 49 | This convention is based on [AngularJS](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) and [ESLint](https://eslint.org/docs/developer-guide/contributing/pull-requests#step2) 50 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | ## Version 11 | 12 | 13 | ## Test Environment 14 | 15 | 16 | ## Current Behavior 17 | 19 | 20 | ```js 21 | // Write example code 22 | ``` 23 | 24 | ## Expected Behavior 25 | 26 | -------------------------------------------------------------------------------- /docs/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 27 | 28 | ### Please check if the PR fulfills these requirements 29 | - [ ] It's the right issue type on the title 30 | - [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number) 31 | - [ ] The commit message follows our guidelines 32 | - [ ] Tests for the changes have been added (for bug fixes/features) 33 | - [ ] Docs have been added/updated (for bug fixes/features) 34 | - [ ] It does not introduce a breaking change or has a description of the breaking change 35 | 36 | ### Description 37 | 38 | 39 | 40 | --- 41 | Thank you for your contribution to TOAST UI product. 🎉 😘 ✨ 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@toast-ui/react-grid", 3 | "version": "2.0.4", 4 | "description": "TOAST UI Grid for React", 5 | "main": "dist/toastui-react-grid.js", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "scripts": { 11 | "lint": "eslint src/**", 12 | "build": "webpack -p --progress", 13 | "storybook": "start-storybook -p 6006", 14 | "build-storybook": "build-storybook" 15 | }, 16 | "homepage": "https://github.com/nhn/toast-ui.react-grid", 17 | "bugs": "https://github.com/nhn/toast-ui.react-grid/issues", 18 | "author": "NHN. FE Development Lab ", 19 | "repository": "https://github.com/nhn/toast-ui.react-grid.git", 20 | "license": "MIT", 21 | "browserslist": "last 2 versions, ie 9", 22 | "peerDependencies": { 23 | "react": "^16.7.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.2.2", 27 | "@babel/plugin-proposal-class-properties": "^7.2.3", 28 | "@babel/plugin-proposal-object-rest-spread": "^7.2.0", 29 | "@babel/preset-env": "^7.2.3", 30 | "@babel/preset-react": "^7.0.0", 31 | "@storybook/addon-actions": "^4.1.7", 32 | "@storybook/addon-knobs": "^4.1.7", 33 | "@storybook/addon-links": "^4.1.7", 34 | "@storybook/addons": "^4.1.7", 35 | "@storybook/react": "^4.1.7", 36 | "babel-eslint": "^10.0.1", 37 | "babel-loader": "^8.0.5", 38 | "css-loader": "^2.1.0", 39 | "eslint": "^5.12.1", 40 | "eslint-config-prettier": "^3.6.0", 41 | "eslint-config-tui": "^2.1.0", 42 | "eslint-plugin-prettier": "^3.0.1", 43 | "eslint-plugin-react": "^7.12.4", 44 | "jquery-mockjax": "^2.5.0", 45 | "prettier": "^1.16.0", 46 | "react": "^16.7.0", 47 | "react-dom": "^16.7.0", 48 | "storybook": "^1.0.0", 49 | "style-loader": "^0.23.1", 50 | "tui-grid": "^4.5.0", 51 | "webpack": "^4.29.0", 52 | "webpack-cli": "^3.2.1", 53 | "webpack-dev-server": "^3.1.14", 54 | "xhr-mock": "^2.4.1" 55 | }, 56 | "dependencies": {} 57 | } 58 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TuiGrid from 'tui-grid'; 3 | 4 | const reactivePropSetterMap = { 5 | data: 'resetData', 6 | columns: 'setColumns', 7 | bodyHeight: 'setBodyHeight', 8 | frozenColumnCount: 'setFrozenColumnCount', 9 | columnOptions: 'setFrozenColumnCount' 10 | }; 11 | 12 | export default class Grid extends React.Component { 13 | rootEl = React.createRef(); 14 | 15 | gridInst = null; 16 | 17 | bindEventHandlers(props, prevProps) { 18 | Object.keys(props) 19 | .filter(key => /on(?!Grid)[A-Z][a-zA-Z]+/.test(key)) 20 | .forEach(key => { 21 | const eventName = key[2].toLowerCase() + key.slice(3); 22 | // For 23 | if (prevProps && prevProps[key] !== props[key]) { 24 | this.gridInst.off(eventName); 25 | } 26 | this.gridInst.on(eventName, props[key]); 27 | }); 28 | } 29 | 30 | getInstance() { 31 | return this.gridInst; 32 | } 33 | 34 | getRootElement() { 35 | return this.rootEl.current; 36 | } 37 | 38 | componentDidMount() { 39 | const { frozenColumnCount: frozenCount, columnOptions: columnOptionsProp = {} } = this.props; 40 | 41 | const columnOptions = 42 | typeof frozenCount === 'number' 43 | ? { 44 | ...columnOptionsProp, 45 | frozenCount 46 | } 47 | : { ...columnOptionsProp }; 48 | 49 | this.gridInst = new TuiGrid({ 50 | el: this.rootEl.current, 51 | ...this.props, 52 | columnOptions 53 | }); 54 | this.bindEventHandlers(this.props); 55 | } 56 | 57 | componentWillUnmount() { 58 | this.gridInst.destroy(); 59 | } 60 | 61 | shouldComponentUpdate(nextProps) { 62 | const { oneTimeBindingProps = [] } = this.props; 63 | const reactiveProps = Object.keys(reactivePropSetterMap).filter( 64 | propName => oneTimeBindingProps.indexOf(propName) === -1 65 | ); 66 | 67 | reactiveProps.forEach(propName => { 68 | let currentValue, nextValue; 69 | if (propName === 'columnOptions' && this.props.columnOptions) { 70 | currentValue = this.props.columnOptions.frozenCount; 71 | nextValue = nextProps.columnOptions.frozenCount; 72 | } else { 73 | currentValue = this.props[propName]; 74 | nextValue = nextProps[propName]; 75 | } 76 | 77 | if (currentValue !== nextValue) { 78 | const setterName = reactivePropSetterMap[propName]; 79 | this.gridInst[setterName](nextValue); 80 | } 81 | }); 82 | 83 | this.bindEventHandlers(nextProps, this.props); 84 | 85 | return false; 86 | } 87 | 88 | render() { 89 | return
; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /stories/dummy-data.js: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { 3 | id: 549731, 4 | name: 'Beautiful Lies', 5 | artist: 'Birdy', 6 | release: '2016.03.26', 7 | type: 'Deluxe', 8 | genre: 'Pop' 9 | }, 10 | { 11 | id: 436461, 12 | name: 'X', 13 | artist: 'Ed Sheeran', 14 | release: '2014.06.24', 15 | type: 'Deluxe', 16 | genre: 'Pop' 17 | }, 18 | { 19 | id: 295651, 20 | name: 'Moves Like Jagger', 21 | release: '2011.08.08', 22 | artist: 'Maroon5', 23 | type: 'Single', 24 | genre: 'Pop,Rock' 25 | }, 26 | { 27 | id: 541713, 28 | name: 'A Head Full Of Dreams', 29 | artist: 'Coldplay', 30 | release: '2015.12.04', 31 | type: 'Deluxe', 32 | genre: 'Rock' 33 | }, 34 | { 35 | id: 265289, 36 | name: '21', 37 | artist: 'Adele', 38 | release: '2011.01.21', 39 | type: 'Deluxe', 40 | genre: 'Pop,R&B' 41 | }, 42 | { 43 | id: 555871, 44 | name: 'Warm On A Cold Night', 45 | artist: 'HONNE', 46 | release: '2016.07.22', 47 | type: 'EP', 48 | genre: 'R&B,Electronic' 49 | }, 50 | { 51 | id: 550571, 52 | name: 'Take Me To The Alley', 53 | artist: 'Gregory Porter', 54 | release: '2016.09.02', 55 | type: 'Deluxe', 56 | genre: 'Jazz' 57 | }, 58 | { 59 | id: 544128, 60 | name: 'Make Out', 61 | artist: 'LANY', 62 | release: '2015.12.11', 63 | type: 'EP', 64 | genre: 'Electronic' 65 | }, 66 | { 67 | id: 366374, 68 | name: 'Get Lucky', 69 | artist: 'Daft Punk', 70 | release: '2013.04.23', 71 | type: 'Single', 72 | genre: 'Pop,Funk' 73 | }, 74 | { 75 | id: 8012747, 76 | name: 'Valtari', 77 | artist: 'Sigur Rós', 78 | release: '2012.05.31', 79 | type: 'EP', 80 | genre: 'Rock' 81 | }, 82 | { 83 | id: 502792, 84 | name: 'Bush', 85 | artist: 'Snoop Dogg', 86 | release: '2015.05.12', 87 | type: 'EP', 88 | genre: 'Hiphop' 89 | }, 90 | { 91 | id: 294574, 92 | name: '4', 93 | artist: 'Beyoncé', 94 | release: '2011.07.26', 95 | type: 'Deluxe', 96 | genre: 'Pop' 97 | } 98 | ]; 99 | -------------------------------------------------------------------------------- /stories/index.stories.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import XHRMock from 'xhr-mock'; 3 | import { storiesOf } from '@storybook/react'; 4 | import Grid from '../src/index'; 5 | import TuiGrid from 'tui-grid'; 6 | import { actions } from '@storybook/addon-actions'; 7 | import { withKnobs, number, radios, button, object, array } from '@storybook/addon-knobs'; 8 | import { data } from './dummy-data'; 9 | import 'tui-grid/dist/tui-grid.css'; 10 | import 'tui-pagination/dist/tui-pagination.css'; 11 | 12 | const columns = [ 13 | { header: 'Name', name: 'name' }, 14 | { header: 'Artist', name: 'artist' }, 15 | { header: 'Type', name: 'type', editor: 'text' }, 16 | { header: 'Release', name: 'release', editor: 'text' }, 17 | { header: 'Genre', name: 'genre', editor: 'text' } 18 | ]; 19 | 20 | const stories = storiesOf('Toast UI Grid', module).addDecorator(withKnobs); 21 | 22 | stories.add('Normal', () => ); 23 | 24 | stories.add('Set Language', () => { 25 | const options = { 26 | ko: 'ko', 27 | en: 'en' 28 | }; 29 | const lang = radios('Language', options, 'ko'); 30 | const Story = () => { 31 | TuiGrid.setLanguage(lang); 32 | 33 | return ; 34 | }; 35 | 36 | return ; 37 | }); 38 | 39 | stories.add('Apply Theme', () => { 40 | const options = { 41 | normal: 'normal', 42 | striped: 'striped', 43 | clean: 'clean' 44 | }; 45 | const theme = radios('Theme', options, 'normal'); 46 | const Story = () => { 47 | TuiGrid.applyTheme(theme); 48 | 49 | return ; 50 | }; 51 | 52 | return ; 53 | }); 54 | 55 | stories.add('getRootElement', () => { 56 | class Story extends React.Component { 57 | ref = React.createRef(); 58 | 59 | handleClick = () => { 60 | alert('see console!'); 61 | console.log(this.ref.current.getRootElement()); 62 | }; 63 | 64 | render() { 65 | return ( 66 |
67 | 68 | 69 |
70 | ); 71 | } 72 | } 73 | 74 | return ; 75 | }); 76 | 77 | stories.add('Using Method', () => { 78 | class Story extends React.Component { 79 | ref = React.createRef(); 80 | 81 | grid = null; 82 | 83 | componentDidMount() { 84 | this.grid = this.ref.current.getInstance(); 85 | } 86 | 87 | handleClickAppend = () => { 88 | this.grid.appendRow({}, { at: 0 }); 89 | }; 90 | 91 | handleClickSort = () => { 92 | this.grid.sort('type'); 93 | }; 94 | 95 | handleClickUnSort = () => { 96 | this.grid.unsort(); 97 | }; 98 | 99 | render() { 100 | return ( 101 |
102 | 103 | 104 | 105 | 106 |
107 | ); 108 | } 109 | } 110 | 111 | return ; 112 | }); 113 | 114 | stories.add('Events', () => { 115 | const eventsFromObject = actions('onClick', 'onDblclick', 'onMousedown'); 116 | 117 | return ; 118 | }); 119 | 120 | stories.add('Reactive Props', () => { 121 | const dataValue = object('data', data.slice(0, 5)); 122 | const columnsValue = object('columns', columns); 123 | const columnOptions = object('columnOptions', {frozenCount: 2}); 124 | const bodyHeightValue = number('bodyHeight', 300, { 125 | range: true, 126 | min: 100, 127 | max: 500 128 | }); 129 | const frozenColumnCountValue = number('frozenColumnCount', 0, { 130 | range: true, 131 | min: 0, 132 | max: 4 133 | }); 134 | const oneTimeBindingProps = array('oneTimeBindingProps', []); 135 | 136 | return ( 137 | 146 | ); 147 | }); 148 | 149 | stories.add('dataSource', () => { 150 | XHRMock.setup(); 151 | 152 | XHRMock.get('api/readData?perPage=3&page=1', { 153 | status: 200, 154 | body: JSON.stringify({ 155 | result: true, 156 | data: { 157 | contents: data.slice(0, 3), 158 | pagination: { 159 | page: 1, 160 | totalCount: data.length 161 | } 162 | } 163 | }) 164 | }) 165 | .get('api/readData?perPage=3&page=2', { 166 | status: 200, 167 | body: JSON.stringify({ 168 | result: true, 169 | data: { 170 | contents: data.slice(3, 6), 171 | pagination: { 172 | page: 2, 173 | totalCount: data.length 174 | } 175 | } 176 | }) 177 | }) 178 | .get('api/readData?perPage=3&page=3', { 179 | status: 200, 180 | body: JSON.stringify({ 181 | result: true, 182 | data: { 183 | contents: data.slice(6, 9), 184 | pagination: { 185 | page: 3, 186 | totalCount: data.length 187 | } 188 | } 189 | }) 190 | }) 191 | .get('api/readData?perPage=3&page=4', { 192 | status: 200, 193 | body: JSON.stringify({ 194 | result: true, 195 | data: { 196 | contents: data.slice(9, 12), 197 | pagination: { 198 | page: 4, 199 | totalCount: data.length 200 | } 201 | } 202 | }) 203 | }); 204 | 205 | const dataSource = { 206 | withCredentials: false, 207 | initialRequest: true, 208 | api: { 209 | readData: { url: 'api/readData', method: 'GET' } 210 | } 211 | }; 212 | 213 | return ( 214 | 215 | ); 216 | }); 217 | 218 | stories.add('hook', () => { 219 | const condition = radios('condition', { true: 'true', false: 'false' }, 'true'); 220 | const ReactComponent = () => { 221 | const onClick = useCallback(() => { 222 | console.log('condition:', condition); 223 | }, [condition]); 224 | 225 | return ; 226 | }; 227 | 228 | return ; 229 | }); 230 | 231 | stories.add('change event based on condition', () => { 232 | const condition = radios('condition', { true: 'true', false: 'false' }, 'true'); 233 | 234 | return ( 235 | { 241 | console.log('true'); 242 | } 243 | : () => { 244 | console.log('false'); 245 | } 246 | } 247 | /> 248 | ); 249 | }); 250 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const config = { 4 | entry: './src/index.js', 5 | output: { 6 | filename: 'toastui-react-grid.js', 7 | path: path.resolve(__dirname, 'dist'), 8 | libraryTarget: 'commonjs2' 9 | }, 10 | externals: { 11 | 'tui-grid': { 12 | commonjs: 'tui-grid', 13 | commonjs2: 'tui-grid' 14 | }, 15 | react: { 16 | commonjs: 'react', 17 | commonjs2: 'react' 18 | } 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.js$/, 24 | include: [path.resolve(__dirname, 'src')], 25 | use: { 26 | loader: 'babel-loader', 27 | options: { 28 | presets: ['@babel/preset-env'] 29 | } 30 | } 31 | } 32 | ] 33 | } 34 | }; 35 | 36 | module.exports = () => config; 37 | --------------------------------------------------------------------------------