diff --git a/README.md b/README.md
index 241d432db..3098a1096 100644
--- a/README.md
+++ b/README.md
@@ -40,75 +40,94 @@ import ReactDOM from 'react-dom';
## Table of Contents
-
-### Array
+### Array
View contents
-* [DataList](#datalist)
-* [DataTable](#datatable)
-* [MappedTable](#mappedtable)
+* [`DataList`](#datalist)
+* [`DataTable`](#datatable)
+* [`MappedTable`](#mappedtable)
+
-
-### Input
+### Hooks
View contents
-* [Input](#input)
-* [LimitedTextarea](#limitedtextarea)
-* [LimitedWordTextarea](#limitedwordtextarea)
-* [MultiselectCheckbox](#multiselectcheckbox)
-* [PasswordRevealer](#passwordrevealer)
-* [Select](#select)
-* [Slider](#slider)
-* [TextArea](#textarea)
+* [`ClickInside and ClickOutside`](#clickinside-and-clickoutside)
+
-
-### Object
+### Input
View contents
-* [TreeView](#treeview)
+* [`LimitedTextarea`](#limitedtextarea)
+* [`LimitedWordTextarea`](#limitedwordtextarea)
+* [`MultiselectCheckbox`](#multiselectcheckbox)
+* [`PasswordRevealer`](#passwordrevealer)
+* [`Select`](#select)
+* [`Slider`](#slider)
+* [`TextArea`](#textarea)
+* [`UncontrolledInput`](#uncontrolledinput)
+
-
-### String
+### Object
View contents
-* [AutoLink](#autolink)
+* [`TreeView`](#treeview-)
+
-
-### Visual
+### String
View contents
-* [Accordion](#accordion)
-* [Carousel](#carousel)
-* [Collapse](#collapse)
-* [CountDown](#countdown)
-* [FileDrop](#filedrop)
-* [Mailto](#mailto)
-* [Modal](#modal)
-* [StarRating](#starrating)
-* [Tabs](#tabs)
-* [Ticker](#ticker)
-* [Toggle](#toggle)
-* [Tooltip](#tooltip)
+* [`AutoLink`](#autolink-)
+
+
+
+### Visual
+
+
+View contents
+
+* [`Accordion`](#accordion-)
+* [`Carousel`](#carousel)
+* [`Collapse`](#collapse)
+* [`CountDown`](#countdown-)
+* [`FileDrop`](#filedrop)
+* [`Modal`](#modal)
+* [`StarRating`](#starrating)
+* [`Tabs`](#tabs)
+* [`Ticker`](#ticker)
+* [`Toggle`](#toggle)
+* [`Tooltip`](#tooltip)
+
+
+
+### Viual
+
+
+View contents
+
+* [`Mailto`](#mailto)
+
---
-## Array
+## Array
+
+
### DataList
Renders a list of elements from an array of primitives.
@@ -134,7 +153,7 @@ ReactDOM.render(, document.getElementById('ro
```
- [⬆ Back to top](#table-of-contents)
+ [⬆ Back to top](#contents)
### DataTable
@@ -175,7 +194,7 @@ ReactDOM.render(, document.getElementById('root'));
```
- [⬆ Back to top](#table-of-contents)
+ [⬆ Back to top](#contents)
### MappedTable
@@ -186,6 +205,8 @@ Renders a table with rows dynamically created from an array of objects and a lis
* Use `Array.prototype.map` to render each value in the `propertyNames` array as a `
` element.
* Use `Array.prototype.map` to render each object in the `filteredData` array as a `
` element, containing a `
` for each key in the object.
+*This component does not work with nested objects and will break if there are nested objects inside any of the properties specified in `propertyNames`*
+
```jsx
function MappedTable({ data, propertyNames }) {
let filteredData = data.map(v =>
@@ -215,9 +236,6 @@ function MappedTable({ data, propertyNames }) {
);
}
```
-#### Notes
-
-This component does not work with nested objects and will break if there are nested objects inside any of the properties specified in `propertyNames`.,,
Examples
@@ -235,27 +253,79 @@ ReactDOM.render(
```
- [⬆ Back to top](#table-of-contents)
+ [⬆ Back to top](#contents)
+
+---
+
+## Hooks
-## Input
-### Input
+### ClickInside and ClickOutside
-Renders an `` element that uses a callback function to pass its value to the parent component.
+Two handy hooks to handle the click outside and inside event on the wrapped component.
-* Use object destructuring to set defaults for certain attributes of the `` element.
-* Render an `` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the input to the parent.
+* Create customized hooks that take in a `ref` component(node) and a `callback` function to hanlde the customized `click` event
+* Use the `React.useEffect()` hook to append and clean up the `click` event.
+* Use the `React.useRef()` hook to create a `ref` for your click component and pass it to `useClickInside` and `useClickOutside` hooks.
+
+```css
+.click-box {
+ border: 2px dashed orangered;
+ height: 200px;
+ width: 400px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+p {
+ border: 2px solid blue;
+ padding: 16px;
+}
+```
```jsx
-function Input({ callback, type = 'text', disabled = false, readOnly = false, placeholder = '' }) {
+const useClickInside = (ref, callback) => {
+ const handleClick = e => {
+ //use the node contains to verify if we click inside
+ if (ref.current && ref.current.contains(e.target)) {
+ callback();
+ }
+ };
+
+ //clean up using useEffect
+ useEffect(() => {
+ document.addEventListener("click", handleClick);
+ return () => {
+ document.removeEventListener("click", handleClick);
+ };
+ });
+};
+
+const useClickOutside = (ref, callback) => {
+ const handleClick = e => {
+ //use the node contains to verify if we click outside
+ if (ref.current && !ref.current.contains(e.target)) {
+ callback();
+ }
+ };
+ // clean up using useEffect
+ useEffect(() => {
+ document.addEventListener("click", handleClick);
+ return () => {
+ document.removeEventListener("click", handleClick);
+ };
+ });
+};
+
+function ClickBox({onClickOutside,onClickInside}) {
+ const clickRef = useRef();
+ useClickOutside(clickRef, onClickOutside);
+ useClickInside(clickRef, onClickInside);
return (
- callback(value)}
- />
+
\n ]}\n />,\n document.getElementById('root')\n);\n```"
- ],
- "expertise": 2,
- "tags": [
- "visual",
- "children",
- "state",
- "effect"
- ],
- "notes": []
- },
- {
- "name": "Collapse.md",
- "title": "Collapse",
- "text": "Renders a component with collapsible content.\n\n* Use the `React.setState()` hook to create the `isCollapsed` state variable with an initial value of `props.collapsed`.\n* Use an object, `style`, to hold the styles for individual components and their states.\n* Use a `
` to wrap both the `` that alters the component's `isCollapsed` state and the content of the component, passed down via `props.children`.\n* Determine the appearance of the content, based on `isCollapsed` and apply the appropriate CSS rules from the `style` object.\n* Finally, update the value of the `aria-expanded` attribute based on `isCollapsed` to make the component accessible.\n\n",
- "codeBlocks": [
- "```jsx\nfunction Collapse(props) {\n const [isCollapsed, setIsCollapsed] = React.useState(props.collapsed);\n\n const style = {\n collapsed: {\n display: 'none'\n },\n expanded: {\n display: 'block'\n },\n buttonStyle: {\n display: 'block',\n width: '100%'\n }\n };\n\n return (\n
\n ,\n document.getElementById('root')\n);\n```"
- ],
- "expertise": 2,
- "tags": [
- "visual",
- "children",
- "state"
- ],
- "notes": []
- },
- {
- "name": "CountDown.md",
- "title": "CountDown",
- "text": "Renders a countdown timer that prints a message when it reaches zero.\n\n* Use object destructuring to set defaults for the `hours`, `minutes` and `seconds` props.\n* Use the `React.useState()` hook to create the `time`, `paused` and `over` state variables and set their values to the values of the passed props, `false` and `false` respectively.\n* Create a method `tick`, that updates the value of `time` based on the current value (i.e. decreasing the time by one second).\n* If `paused` or `over` is `true`, `tick` will return immediately.\n* Create a method `reset`, that resets all state variables to their initial states.\n* Use the the `React.useEffect()` hook to call the `tick` method every second via the use of `setInterval()` and use `clearInterval()` to cleanup when the component is unmounted.\n* Use a `
` to wrap a `
` element with the textual representation of the components `time` state variable, as well as two `` elements that will pause/unpause and restart the timer respectively.\n* If `over` is `true`, the timer will display a message instead of the value of `time`.\n\n",
- "codeBlocks": [
- "```jsx\nfunction CountDown({ hours = 0, minutes = 0, seconds = 0 }) {\n const [paused, setPaused] = React.useState(false);\n const [over, setOver] = React.useState(false);\n const [time, setTime] = React.useState({\n hours: parseInt(hours),\n minutes: parseInt(minutes),\n seconds: parseInt(seconds)\n });\n\n const tick = () => {\n if (paused || over) return;\n if (time.hours == 0 && time.minutes == 0 && time.seconds == 0) setOver(true);\n else if (time.minutes == 0 && time.seconds == 0)\n setTime({\n hours: time.hours - 1,\n minutes: 59,\n seconds: 59\n });\n else if (time.seconds == 0)\n setTime({\n hours: time.hours,\n minutes: time.minutes - 1,\n seconds: 59\n });\n else\n setTime({\n hours: time.hours,\n minutes: time.minutes,\n seconds: time.seconds - 1\n });\n };\n\n const reset = () => {\n setTime({\n hours: parseInt(hours),\n minutes: parseInt(minutes),\n seconds: parseInt(seconds)\n });\n setPaused(false);\n setOver(false);\n };\n\n React.useEffect(() => {\n let timerID = setInterval(() => tick(), 1000);\n return () => clearInterval(timerID);\n });\n\n return (\n
\n );\n}\n```",
- "```jsx\nReactDOM.render(, document.getElementById('root'));\n```"
- ],
- "expertise": 2,
- "tags": [
- "visual",
- "state"
- ],
- "notes": []
- },
- {
- "name": "DataList.md",
- "title": "DataList",
- "text": "Renders a list of elements from an array of primitives.\n\n* Use the value of the `isOrdered` prop to conditionally render a `` or `
` list.\n* Use `Array.prototype.map` to render every item in `data` as a `
` element, give it a `key` produced from the concatenation of the its index and value.\n* Omit the `isOrdered` prop to render a `
` list by default.\n\n",
- "codeBlocks": [
- "```jsx\nfunction DataList({ isOrdered, data }) {\n const list = data.map((val, i) =>
{val}
);\n return isOrdered ? {list} :
{list}
;\n}\n```",
- "```jsx\nconst names = ['John', 'Paul', 'Mary'];\nReactDOM.render(, document.getElementById('root'));\nReactDOM.render(, document.getElementById('root'));\n```"
- ],
- "expertise": 0,
- "tags": [
- "array"
- ],
- "notes": []
- },
- {
- "name": "DataTable.md",
- "title": "DataTable",
- "text": "Renders a table with rows dynamically created from an array of primitives.\n\n* Render a `
` element with two columns (`ID` and `Value`).\n* Use `Array.prototype.map` to render every item in `data` as a `
` element, consisting of its index and value, give it a `key` produced from the concatenation of the two.\n\n",
- "codeBlocks": [
- "```jsx\nfunction DataTable({ data }) {\n return (\n
\n \n
\n
ID
\n
Value
\n
\n \n \n {data.map((val, i) => (\n
\n
{i}
\n
{val}
\n
\n ))}\n \n
\n );\n}\n```",
- "```jsx\nconst people = ['John', 'Jesse'];\nReactDOM.render(, document.getElementById('root'));\n```"
- ],
- "expertise": 0,
- "tags": [
- "array"
- ],
- "notes": []
- },
- {
- "name": "FileDrop.md",
- "title": "FileDrop",
- "text": "Renders a file drag and drop component for a single file.\n\n* Create a ref called `dropRef` for this component.\n* Use the `React.useState()` hook to create the `drag` and `filename` variables, initialized to `false` and `''` respectively.\nThe variables `dragCounter` and `drag` are used to determine if a file is being dragged, while `filename` is used to store the dropped file's name.\n* Create the `handleDrag`, `handleDragIn`, `handleDragOut` and `handleDrop` methods to handle drag and drop functionality, bind them to the component's context.\n* Each of the methods will handle a specific event, the listeners for which are created and removed in the `React.useEffect()` hook and its attached `cleanup()` method.\n* `handleDrag` prevents the browser from opening the dragged file, `handleDragIn` and `handleDragOut` handle the dragged file entering and exiting the component, while `handleDrop` handles the file being dropped and passes it to `props.handleDrop`.\n* Return an appropriately styled `
` and use `drag` and `filename` to determine its contents and style.\n* Finally, bind the `ref` of the created `
\n );\n}\n```",
- "```jsx\nReactDOM.render(, document.getElementById('root'));\n```"
- ],
- "expertise": 2,
- "tags": [
- "visual",
- "input",
- "state",
- "effect"
- ],
- "notes": []
- },
- {
- "name": "Input.md",
- "title": "Input",
- "text": "Renders an `` element that uses a callback function to pass its value to the parent component.\n\n* Use object destructuring to set defaults for certain attributes of the `` element.\n* Render an `` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the input to the parent.\n\n",
- "codeBlocks": [
- "```jsx\nfunction Input({ callback, type = 'text', disabled = false, readOnly = false, placeholder = '' }) {\n return (\n callback(value)}\n />\n );\n}\n```",
- "```jsx\nReactDOM.render(\n console.log(val)} />,\n document.getElementById('root')\n);\n```"
- ],
- "expertise": 0,
- "tags": [
- "input"
- ],
- "notes": []
- },
- {
- "name": "LimitedTextarea.md",
- "title": "LimitedTextarea",
- "text": "Renders a textarea component with a character limit.\n\n* Use the `React.useState()` hook to create the `content` state variable and set its value to `value`.\nCreate a method `setFormattedContent`, which trims the content of the input if it's longer than `limit`.\n* Use the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.\n* Use a`