Initial testing of extractor and builder
Configured appropriately
This commit is contained in:
389
README.md
389
README.md
@ -40,75 +40,94 @@ import ReactDOM from 'react-dom';
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
||||
### Array
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [DataList](#datalist)
|
||||
* [DataTable](#datatable)
|
||||
* [MappedTable](#mappedtable)
|
||||
* [`DataList`](#datalist)
|
||||
* [`DataTable`](#datatable)
|
||||
* [`MappedTable`](#mappedtable)
|
||||
|
||||
</details>
|
||||
|
||||
### Hooks
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [`ClickInside and ClickOutside`](#clickinside-and-clickoutside)
|
||||
|
||||
</details>
|
||||
|
||||
### Input
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [Input](#input)
|
||||
* [LimitedTextarea](#limitedtextarea)
|
||||
* [LimitedWordTextarea](#limitedwordtextarea)
|
||||
* [MultiselectCheckbox](#multiselectcheckbox)
|
||||
* [PasswordRevealer](#passwordrevealer)
|
||||
* [Select](#select)
|
||||
* [Slider](#slider)
|
||||
* [TextArea](#textarea)
|
||||
</details>
|
||||
* [`LimitedTextarea`](#limitedtextarea)
|
||||
* [`LimitedWordTextarea`](#limitedwordtextarea)
|
||||
* [`MultiselectCheckbox`](#multiselectcheckbox)
|
||||
* [`PasswordRevealer`](#passwordrevealer)
|
||||
* [`Select`](#select)
|
||||
* [`Slider`](#slider)
|
||||
* [`TextArea`](#textarea)
|
||||
* [`UncontrolledInput`](#uncontrolledinput)
|
||||
|
||||
</details>
|
||||
|
||||
### Object
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [TreeView](#treeview)
|
||||
</details>
|
||||
* [`TreeView`](#treeview-)
|
||||
|
||||
</details>
|
||||
|
||||
### String
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [AutoLink](#autolink)
|
||||
</details>
|
||||
* [`AutoLink`](#autolink-)
|
||||
|
||||
</details>
|
||||
|
||||
### Visual
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [Accordion](#accordion)
|
||||
* [Carousel](#carousel)
|
||||
* [Collapse](#collapse)
|
||||
* [CountDown](#countdown)
|
||||
* [FileDrop](#filedrop)
|
||||
* [Mailto](#mailto)
|
||||
* [Modal](#modal)
|
||||
* [StarRating](#starrating)
|
||||
* [Tabs](#tabs)
|
||||
* [Ticker](#ticker)
|
||||
* [Toggle](#toggle)
|
||||
* [Tooltip](#tooltip)
|
||||
* [`Accordion`](#accordion-)
|
||||
* [`Carousel`](#carousel)
|
||||
* [`Collapse`](#collapse)
|
||||
* [`CountDown`](#countdown-)
|
||||
* [`FileDrop`](#filedrop)
|
||||
* [`Modal`](#modal)
|
||||
* [`StarRating`](#starrating)
|
||||
* [`Tabs`](#tabs)
|
||||
* [`Ticker`](#ticker)
|
||||
* [`Toggle`](#toggle)
|
||||
* [`Tooltip`](#tooltip)
|
||||
|
||||
</details>
|
||||
|
||||
### Viual
|
||||
|
||||
<details>
|
||||
<summary>View contents</summary>
|
||||
|
||||
* [`Mailto`](#mailto)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Array
|
||||
|
||||
|
||||
### DataList
|
||||
|
||||
Renders a list of elements from an array of primitives.
|
||||
@ -134,7 +153,7 @@ ReactDOM.render(<DataList data={names} isOrdered />, document.getElementById('ro
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### DataTable
|
||||
|
||||
@ -175,7 +194,7 @@ ReactDOM.render(<DataTable data={people} />, document.getElementById('root'));
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ 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 `<th>` element.
|
||||
* Use `Array.prototype.map` to render each object in the `filteredData` array as a `<tr>` element, containing a `<td>` 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`.,<!-tags: array,object -->,<!-expertise: 1 -->
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
@ -235,27 +253,79 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
## Hooks
|
||||
|
||||
|
||||
## Input
|
||||
### Input
|
||||
### ClickInside and ClickOutside
|
||||
|
||||
Renders an `<input>` 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 `<input>` element.
|
||||
* Render an `<input>` 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 (
|
||||
<input
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
placeholder={placeholder}
|
||||
onChange={({ target: { value } }) => callback(value)}
|
||||
/>
|
||||
<div className="click-box" ref={clickRef}>
|
||||
<p>Hello Click Me Inside!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
@ -264,14 +334,16 @@ function Input({ callback, type = 'text', disabled = false, readOnly = false, pl
|
||||
<summary>Examples</summary>
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Input type="text" placeholder="Insert some text here..." callback={val => console.log(val)} />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
ReactDOM.render(<ClickBox onClickOutside={()=> alert("click outside")} onClickInside={()=> alert("click inside")}/>,document.getElementById('root'))
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
## Input
|
||||
|
||||
|
||||
### LimitedTextarea
|
||||
|
||||
@ -318,7 +390,7 @@ ReactDOM.render(<LimitedTextarea limit={32} value="Hello!" />, document.getEleme
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### LimitedWordTextarea
|
||||
|
||||
@ -382,7 +454,7 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### MultiselectCheckbox
|
||||
|
||||
@ -409,7 +481,7 @@ function MultiselectCheckbox({ options, onChange }) {
|
||||
const [data, setData] = React.useState(options);
|
||||
|
||||
const toggle = item => {
|
||||
data.map((_, key) => {
|
||||
data.forEach((_, key) => {
|
||||
if (data[key].label === item.label) data[key].checked = !item.checked;
|
||||
});
|
||||
setData([...data]);
|
||||
@ -449,7 +521,7 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### PasswordRevealer
|
||||
|
||||
@ -479,7 +551,7 @@ ReactDOM.render(<PasswordRevealer />, document.getElementById('root'));
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Select
|
||||
|
||||
@ -524,7 +596,7 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Slider
|
||||
|
||||
@ -554,7 +626,7 @@ ReactDOM.render(<Slider callback={val => console.log(val)} />, document.getEleme
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### TextArea
|
||||
|
||||
@ -596,11 +668,48 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### UncontrolledInput
|
||||
|
||||
Renders an `<input>` element that uses a callback function to pass its value to the parent component.
|
||||
|
||||
* Use object destructuring to set defaults for certain attributes of the `<input>` element.
|
||||
* Render an `<input>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the input to the parent.
|
||||
|
||||
```jsx
|
||||
function UncontrolledInput({ callback, type = 'text', disabled = false, readOnly = false, placeholder = '' }) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
placeholder={placeholder}
|
||||
onChange={({ target: { value } }) => callback(value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<UncontrolledInput type="text" placeholder="Insert some text here..." callback={val => console.log(val)} />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
## Object
|
||||
### TreeView
|
||||
|
||||
|
||||
### TreeView 
|
||||
|
||||
Renders a tree view of a JSON object or array with collapsible content.
|
||||
|
||||
@ -728,11 +837,14 @@ ReactDOM.render(<TreeView data={data} name="data" />, document.getElementById('r
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
## String
|
||||
### AutoLink
|
||||
|
||||
|
||||
### AutoLink 
|
||||
|
||||
Renders a string as plaintext, with URLs converted to appropriate `<a>` elements.
|
||||
|
||||
@ -769,11 +881,14 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
## Visual
|
||||
### Accordion
|
||||
|
||||
|
||||
### Accordion 
|
||||
|
||||
Renders an accordion menu with multiple collapsible content components.
|
||||
|
||||
@ -857,7 +972,7 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Carousel
|
||||
|
||||
@ -926,7 +1041,7 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Collapse
|
||||
|
||||
@ -986,9 +1101,9 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### CountDown
|
||||
### CountDown 
|
||||
|
||||
Renders a countdown timer that prints a message when it reaches zero.
|
||||
|
||||
@ -1047,7 +1162,7 @@ function CountDown({ hours = 0, minutes = 0, seconds = 0 }) {
|
||||
React.useEffect(() => {
|
||||
let timerID = setInterval(() => tick(), 1000);
|
||||
return () => clearInterval(timerID);
|
||||
}, [tick]);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -1070,7 +1185,7 @@ ReactDOM.render(<CountDown hours="1" minutes="45" />, document.getElementById('r
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### FileDrop
|
||||
|
||||
@ -1175,37 +1290,7 @@ ReactDOM.render(<FileDrop handleDrop={console.log} />, document.getElementById('
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
|
||||
### Mailto
|
||||
|
||||
Renders a link formatted to send an email.
|
||||
|
||||
* Destructure the component's props, use `email`, `subject` and `body` to create a `<a>` element with an appropriate `href` attribute.
|
||||
* Render the link with `props.children` as its content.
|
||||
|
||||
```jsx
|
||||
function Mailto({ email, subject, body, ...props }) {
|
||||
return (
|
||||
<a href={`mailto:${email}?subject=${subject || ''}&body=${body || ''}`}>{props.children}</a>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Mailto email="foo@bar.baz" subject="Hello" body="Hello world!">
|
||||
Mail me!
|
||||
</Mailto>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Modal
|
||||
|
||||
@ -1345,7 +1430,7 @@ ReactDOM.render( <App/>, document.getElementById('root'));
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### StarRating
|
||||
|
||||
@ -1402,7 +1487,7 @@ ReactDOM.render(<StarRating rating={2} />, document.getElementById('root'));
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Tabs
|
||||
|
||||
@ -1485,7 +1570,7 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Ticker
|
||||
|
||||
@ -1496,50 +1581,28 @@ Renders a ticker component.
|
||||
* Return a `<div>` with two `<button>` elements, each of which calls `tick` and `reset` respectively.
|
||||
|
||||
```jsx
|
||||
// https://overreacted.io/making-setinterval-declarative-with-react-hooks/
|
||||
function useInterval(callback, delay) {
|
||||
const savedCallback = React.useRef();
|
||||
|
||||
// Remember the latest callback.
|
||||
React.useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
// Set up the interval.
|
||||
React.useEffect(() => {
|
||||
function tick() {
|
||||
savedCallback.current();
|
||||
}
|
||||
if (delay !== null) {
|
||||
let id = setInterval(tick, delay);
|
||||
return () => clearInterval(id);
|
||||
}
|
||||
}, [delay]);
|
||||
}
|
||||
|
||||
function Ticker(props) {
|
||||
const [ticker, setTicker] = React.useState(0);
|
||||
const [isRunning, setIsRunning] = React.useState(false);
|
||||
useInterval(
|
||||
() => {
|
||||
let interval = null;
|
||||
|
||||
const tick = () => {
|
||||
reset();
|
||||
interval = setInterval(() => {
|
||||
if (ticker < props.times) setTicker(ticker + 1);
|
||||
else setIsRunning(false);
|
||||
},
|
||||
isRunning ? props.interval : null
|
||||
);
|
||||
else clearInterval(interval);
|
||||
}, props.interval);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setTicker(0);
|
||||
clearInterval(interval);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span style={{ fontSize: 100 }}>{ticker}</span>
|
||||
<button onClick={() => setIsRunning(true)}>Tick!</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsRunning(false);
|
||||
setTicker(0);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button onClick={tick}>Tick!</button>
|
||||
<button onClick={reset}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1553,7 +1616,7 @@ ReactDOM.render(<Ticker times={5} interval={1000} />, document.getElementById('r
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Toggle
|
||||
|
||||
@ -1591,7 +1654,7 @@ ReactDOM.render(<Toggle />, document.getElementById('root'));
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
### Tooltip
|
||||
|
||||
@ -1651,8 +1714,42 @@ ReactDOM.render(
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#table-of-contents)
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
## Viual
|
||||
|
||||
|
||||
### Mailto
|
||||
|
||||
Renders a link formatted to send an email.
|
||||
|
||||
* Destructure the component's props, use `email`, `subject` and `body` to create a `<a>` element with an appropriate `href` attribute.
|
||||
* Render the link with `props.children` as its content.
|
||||
|
||||
```jsx
|
||||
function Mailto({ email, subject, body, ...props }) {
|
||||
return (
|
||||
<a href={`mailto:${email}?subject=${subject || ''}&body=${body || ''}`}>{props.children}</a>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Mailto email="foo@bar.baz" subject="Hello" body="Hello world!">
|
||||
Mail me!
|
||||
</Mailto>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
</details>
|
||||
|
||||
<br>[⬆ Back to top](#contents)
|
||||
|
||||
---
|
||||
|
||||
|
||||
16
config.js
Normal file
16
config.js
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
// Project metadata
|
||||
name: `30 seconds starter`,
|
||||
description: `Curated collection of useful React snippets that you can understand in 30 seconds or less.`,
|
||||
shortName: `30s`,
|
||||
repositoryUrl: `https://github.com/30-seconds/30-seconds-of-react`,
|
||||
// Path information
|
||||
snippetPath: `snippets`,
|
||||
snippetDataPath: `snippet_data`,
|
||||
assetPath: `assets`,
|
||||
pagePath: `src/docs/pages`,
|
||||
staticPartsPath: `src/static-parts`,
|
||||
// General information
|
||||
language: `jsx`,
|
||||
optionalLanguage: `css`,
|
||||
};
|
||||
@ -1,395 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Accordion.md",
|
||||
"title": "Accordion",
|
||||
"text": "Renders an accordion menu with multiple collapsible content components.\n\n* Define an `AccordionItem` component, pass it to the `Accordion` and remove unnecessary nodes expect for `AccordionItem` by identifying the function's name in `props.children`.\n* Each `AccordionItem` component renders a `<button>` that is used to update the `Accordion` via the `props.handleClick` callback and the content of the component, passed down via `props.children`, while its appearance is determined by `props.isCollapsed` and based on `style`.\n* In the `Accordion` component, use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.\n* Use `Array.prototype.map` on the collected nodes to render the individual collapsiple elements.\n* Define `changeItem`, which will be executed when clicking an `AccordionItem`'s `<button>`.\n`changeItem` executes the passed callback, `onItemClick` and updates `bindIndex` based on the clicked element.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction AccordionItem(props) {\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 <div>\n <button style={style.buttonStyle} onClick={() => props.handleClick()}>\n {props.label}\n </button>\n <div\n className=\"collapse-content\"\n style={props.isCollapsed ? style.collapsed : style.expanded}\n aria-expanded={props.isCollapsed}\n >\n {props.children}\n </div>\n </div>\n );\n}\n\nfunction Accordion(props) {\n const [bindIndex, setBindIndex] = React.useState(props.defaultIndex);\n\n const changeItem = itemIndex => {\n if (typeof props.onItemClick === 'function') props.onItemClick(itemIndex);\n if (itemIndex !== bindIndex) setBindIndex(itemIndex);\n };\n const items = props.children.filter(item => item.type.name === 'AccordionItem');\n\n return (\n <div className=\"wrapper\">\n {items.map(({ props }) => (\n <AccordionItem\n isCollapsed={bindIndex === props.index}\n label={props.label}\n handleClick={() => changeItem(props.index)}\n children={props.children}\n />\n ))}\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Accordion defaultIndex=\"1\" onItemClick={console.log}>\n <AccordionItem label=\"A\" index=\"1\">\n Lorem ipsum\n </AccordionItem>\n <AccordionItem label=\"B\" index=\"2\">\n Dolor sit amet\n </AccordionItem>\n </Accordion>,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "AutoLink.md",
|
||||
"title": "AutoLink",
|
||||
"text": "Renders a string as plaintext, with URLs converted to appropriate `<a>` elements.\n\n* Use `String.prototype.split()` and `String.prototype.match()` with a regular expression to find URLs in a string.\n* Return a `<React.Fragment>` with matched URLs rendered as `<a>` elements, dealing with missing protocol prefixes if necessary, and the rest of the string rendered as plaintext.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction AutoLink({ text }) {\n const delimiter = /((?:https?:\\/\\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\\-]{1,61}[a-z0-9])?\\.[^\\.|\\s])+[a-z\\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\\d{1,5})*[a-z0-9.,_\\/~#&=;%+?\\-\\\\(\\\\)]*)/gi;\n\n return (\n <React.Fragment>\n {text.split(delimiter).map(word => {\n let match = word.match(delimiter);\n if (match) {\n let url = match[0];\n return <a href={url.startsWith('http') ? url : `http://${url}`}>{url}</a>;\n }\n return word;\n })}\n </React.Fragment>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <AutoLink text=\"foo bar baz http://example.org bar\" />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"string",
|
||||
"fragment",
|
||||
"regexp"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Carousel.md",
|
||||
"title": "Carousel",
|
||||
"text": "Renders a carousel component.\n\n* Use the `React.setState()` hook to create the `active` state variable and give it a value of `0` (index of the first item).\n* Use an object, `style`, to hold the styles for the individual components.\n* Use the `React.setEffect()` hook to update the value of `active` to the index of the next item, using `setTimeout`.\n* Destructure `props`, compute if visibility style should be set to `visible` or not for each carousel item while mapping over and applying the combined style to the carousel item component accordingly.\n* Render the carousel items using `React.cloneElement()` and pass down rest `props` along with the computed styles.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Carousel(props) {\n const [active, setActive] = React.useState(0);\n let scrollInterval = null;\n const style = {\n carousel: {\n position: 'relative'\n },\n carouselItem: {\n position: 'absolute',\n visibility: 'hidden'\n },\n visible: {\n visibility: 'visible'\n }\n };\n React.useEffect(() => {\n scrollInterval = setTimeout(() => {\n const { carouselItems } = props;\n setActive((active + 1) % carouselItems.length);\n }, 2000);\n });\n const { carouselItems, ...rest } = props;\n return (\n <div style={style.carousel}>\n {carouselItems.map((item, index) => {\n const activeStyle = active === index ? style.visible : {};\n return React.cloneElement(item, {\n ...rest,\n style: {\n ...style.carouselItem,\n ...activeStyle\n }\n });\n })}\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Carousel\n carouselItems={[\n <div>carousel item 1</div>,\n <div>carousel item 2</div>,\n <div>carousel item 3</div>\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 `<div>` to wrap both the `<button>` 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 <div>\n <button style={style.buttonStyle} onClick={() => setIsCollapsed(!isCollapsed)}>\n {isCollapsed ? 'Show' : 'Hide'} content\n </button>\n <div\n className=\"collapse-content\"\n style={isCollapsed ? style.collapsed : style.expanded}\n aria-expanded={isCollapsed}\n >\n {props.children}\n </div>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Collapse>\n <h1>This is a collapse</h1>\n <p>Hello world!</p>\n </Collapse>,\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 `<div>` to wrap a `<p>` element with the textual representation of the components `time` state variable, as well as two `<button>` 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 <div>\n <p>{`${time.hours.toString().padStart(2, '0')}:${time.minutes\n .toString()\n .padStart(2, '0')}:${time.seconds.toString().padStart(2, '0')}`}</p>\n <div>{over ? \"Time's up!\" : ''}</div>\n <button onClick={() => setPaused(!paused)}>{paused ? 'Resume' : 'Pause'}</button>\n <button onClick={() => reset()}>Restart</button>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<CountDown hours=\"1\" minutes=\"45\" />, 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 `<ol>` or `<ul>` list.\n* Use `Array.prototype.map` to render every item in `data` as a `<li>` element, give it a `key` produced from the concatenation of the its index and value.\n* Omit the `isOrdered` prop to render a `<ul>` list by default.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction DataList({ isOrdered, data }) {\n const list = data.map((val, i) => <li key={`${i}_${val}`}>{val}</li>);\n return isOrdered ? <ol>{list}</ol> : <ul>{list}</ul>;\n}\n```",
|
||||
"```jsx\nconst names = ['John', 'Paul', 'Mary'];\nReactDOM.render(<DataList data={names} />, document.getElementById('root'));\nReactDOM.render(<DataList data={names} isOrdered />, 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 `<table>` element with two columns (`ID` and `Value`).\n* Use `Array.prototype.map` to render every item in `data` as a `<tr>` 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 <table>\n <thead>\n <tr>\n <th>ID</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n {data.map((val, i) => (\n <tr key={`${i}_${val}`}>\n <td>{i}</td>\n <td>{val}</td>\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n```",
|
||||
"```jsx\nconst people = ['John', 'Jesse'];\nReactDOM.render(<DataTable data={people} />, 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 `<div>` and use `drag` and `filename` to determine its contents and style.\n* Finally, bind the `ref` of the created `<div>` to `dropRef`.\n\n",
|
||||
"codeBlocks": [
|
||||
"```css\n.filedrop {\n min-height: 120px;\n border: 3px solid #d3d3d3;\n text-align: center;\n font-size: 24px;\n padding: 32px;\n border-radius: 4px;\n}\n\n.filedrop.drag {\n border: 3px dashed #1e90ff;\n}\n\n.filedrop.ready {\n border: 3px solid #32cd32;\n}\n```",
|
||||
"```jsx\nfunction FileDrop(props) {\n const [drag, setDrag] = React.useState(false);\n const [filename, setFilename] = React.useState('');\n let dropRef = React.createRef();\n let dragCounter = 0;\n\n const handleDrag = e => {\n e.preventDefault();\n e.stopPropagation();\n };\n\n const handleDragIn = e => {\n e.preventDefault();\n e.stopPropagation();\n dragCounter++;\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) setDrag(true);\n };\n\n const handleDragOut = e => {\n e.preventDefault();\n e.stopPropagation();\n dragCounter--;\n if (dragCounter === 0) setDrag(false);\n };\n\n const handleDrop = e => {\n e.preventDefault();\n e.stopPropagation();\n setDrag(false);\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\n props.handleDrop(e.dataTransfer.files[0]);\n setFilename(e.dataTransfer.files[0].name);\n e.dataTransfer.clearData();\n dragCounter = 0;\n }\n };\n\n React.useEffect(() => {\n let div = dropRef.current;\n div.addEventListener('dragenter', handleDragIn);\n div.addEventListener('dragleave', handleDragOut);\n div.addEventListener('dragover', handleDrag);\n div.addEventListener('drop', handleDrop);\n return function cleanup() {\n div.removeEventListener('dragenter', handleDragIn);\n div.removeEventListener('dragleave', handleDragOut);\n div.removeEventListener('dragover', handleDrag);\n div.removeEventListener('drop', handleDrop);\n };\n });\n\n return (\n <div\n ref={dropRef}\n className={drag ? 'filedrop drag' : filename ? 'filedrop ready' : 'filedrop'}\n >\n {filename && !drag ? <div>{filename}</div> : <div>Drop files here!</div>}\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<FileDrop handleDrop={console.log} />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"visual",
|
||||
"input",
|
||||
"state",
|
||||
"effect"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Input.md",
|
||||
"title": "Input",
|
||||
"text": "Renders an `<input>` 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 `<input>` element.\n* Render an `<input>` 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 <input\n type={type}\n disabled={disabled}\n readOnly={readOnly}\n placeholder={placeholder}\n onChange={({ target: { value } }) => callback(value)}\n />\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Input type=\"text\" placeholder=\"Insert some text here...\" callback={val => 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`<div>` to wrap both the`<textarea>` and the `<p>` element that displays the character count and bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction LimitedTextarea({ rows, cols, value, limit }) {\n const [content, setContent] = React.useState(value);\n\n const setFormattedContent = text => {\n text.length > limit ? setContent(text.slice(0, limit)) : setContent(text);\n };\n\n React.useEffect(() => {\n setFormattedContent(content);\n }, []);\n\n return (\n <div>\n <textarea\n rows={rows}\n cols={cols}\n onChange={event => setFormattedContent(event.target.value)}\n value={content}\n />\n <p>\n {content.length}/{limit}\n </p>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<LimitedTextarea limit={32} value=\"Hello!\" />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "LimitedWordTextarea.md",
|
||||
"title": "LimitedWordTextarea",
|
||||
"text": "Renders a textarea component with a word limit.\n\n* Use the `React.useState()` hook to create the `content` and `wordCount` state variables and set their values to `value` and `0` respectively.\n* Create a method `setFormattedContent`, which uses `String.prototype.split(' ')` to turn the input into an array of words and check if the result of applying `Array.prototype.filter(Boolean)` has a `length` longer than `limit`.\n* If the afforementioned `length` exceeds the `limit`, trim the input, otherwise return the raw input, updating `content` and `wordCount` accordingly in both cases.\n* Use the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.\n* Use a`<div>` to wrap both the`<textarea>` and the `<p>` element that displays the character count and bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction LimitedWordTextarea({ rows, cols, value, limit }) {\n const [content, setContent] = React.useState(value);\n const [wordCount, setWordCount] = React.useState(0);\n\n const setFormattedContent = text => {\n let words = text.split(' ');\n if (words.filter(Boolean).length > limit) {\n setContent(\n text\n .split(' ')\n .slice(0, limit)\n .join(' ')\n );\n setWordCount(limit);\n } else {\n setContent(text);\n setWordCount(words.filter(Boolean).length);\n }\n };\n\n React.useEffect(() => {\n setFormattedContent(content);\n }, []);\n\n return (\n <div>\n <textarea\n rows={rows}\n cols={cols}\n onChange={event => setFormattedContent(event.target.value)}\n value={content}\n />\n <p>\n {wordCount}/{limit}\n </p>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <LimitedWordTextArea limit={5} value=\"Hello there!\" />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Mailto.md",
|
||||
"title": "Mailto",
|
||||
"text": "Renders a link formatted to send an email.\n\n* Destructure the component's props, use `email`, `subject` and `body` to create a `<a>` element with an appropriate `href` attribute.\n* Render the link with `props.children` as its content.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Mailto({ email, subject, body, ...props }) {\n return (\n <a href={`mailto:${email}?subject=${subject || ''}&body=${body || ''}`}>{props.children}</a>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Mailto email=\"foo@bar.baz\" subject=\"Hello\" body=\"Hello world!\">\n Mail me!\n </Mailto>,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"visual"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "MappedTable.md",
|
||||
"title": "MappedTable",
|
||||
"text": "Renders a table with rows dynamically created from an array of objects and a list of property names.\n\n* Use `Object.keys()`, `Array.prototype.filter()`, `Array.prototype.includes()` and `Array.prototype.reduce()` to produce a `filteredData` array, containing all objects with the keys specified in `propertyNames`.\n* Render a `<table>` element with a set of columns equal to the amount of values in `propertyNames`.\n* Use `Array.prototype.map` to render each value in the `propertyNames` array as a `<th>` element.\n* Use `Array.prototype.map` to render each object in the `filteredData` array as a `<tr>` element, containing a `<td>` for each key in the object.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction MappedTable({ data, propertyNames }) {\n let filteredData = data.map(v =>\n Object.keys(v)\n .filter(k => propertyNames.includes(k))\n .reduce((acc, key) => ((acc[key] = v[key]), acc), {})\n );\n return (\n <table>\n <thead>\n <tr>\n {propertyNames.map(val => (\n <th key={`h_${val}`}>{val}</th>\n ))}\n </tr>\n </thead>\n <tbody>\n {filteredData.map((val, i) => (\n <tr key={`i_${i}`}>\n {propertyNames.map(p => (\n <td key={`i_${i}_${p}`}>{val[p]}</td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n```",
|
||||
"```jsx\nconst people = [\n { name: 'John', surname: 'Smith', age: 42 },\n { name: 'Adam', surname: 'Smith', gender: 'male' }\n];\nconst propertyNames = ['name', 'surname', 'age'];\nReactDOM.render(\n <MappedTable data={people} propertyNames={propertyNames} />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 1,
|
||||
"tags": [
|
||||
"array",
|
||||
"object"
|
||||
],
|
||||
"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`.",
|
||||
"<!-tags: array,object -->",
|
||||
"<!-expertise: 1 -->"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Modal.md",
|
||||
"title": "Modal",
|
||||
"text": "Renders a Modal component, controllable through events.\nTo use the component, import `Modal` only once and then display it by passing a boolean value to the `isVisible` attribute.\n\n* Use object destructuring to set defaults for certain attributes of the modal component.\n* Define `keydownHandler`, a method which handles all keyboard events, which can be used according to your needs to dispatch actions (e.g. close the modal when <kbd>Esc</kbd> is pressed).\n* Use `React.useEffect()` hook to add or remove the `keydown` event listener, which calls `keydownHandler`.\n* Use the `isVisible` prop to determine if the modal should be shown or not.\n* Use CSS to style and position the modal component.\n\n",
|
||||
"codeBlocks": [
|
||||
"```css\n.modal {\n position: fixed;\n top: 0;\n bottom: 0;\n left: 0;\n right:0;\n width: 100%;\n z-index: 9999; \n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(0, 0, 0, 0.25);\n animation-name: appear;\n animation-duration: 300ms;\n}\n\n.modal-dialog{\n width: 100%;\n max-width: 550px;\n background: white;\n position: relative;\n margin: 0 20px;\n max-height: calc(100vh - 40px);\n text-align: left;\n display: flex;\n flex-direction: column;\n overflow:hidden;\n box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);\n -webkit-animation-name: animatetop;\n -webkit-animation-duration: 0.4s;\n animation-name: slide-in;\n animation-duration: 0.5s;\n}\n\n.modal-header,.modal-footer{\n display: flex;\n align-items: center;\n padding: 1rem;\n}\n.modal-header{\n border-bottom: 1px solid #dbdbdb;\n justify-content: space-between;\n}\n.modal-footer{\n border-top: 1px solid #dbdbdb;\n justify-content: flex-end;\n}\n.modal-close{\n cursor: pointer;\n padding: 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n.modal-body{\n overflow: auto;\n}\n.modal-content{\n padding: 1rem;\n}\n\n@keyframes appear {\n from {opacity: 0;}\n to {opacity: 1;}\n}\n@keyframes slide-in {\n from {transform: translateY(-150px);}\n to { transform: translateY(0);}\n}\n```",
|
||||
"```jsx\nfunction Modal({ isVisible = false, title, content, footer, onClose }){ \n React.useEffect(() => {\n document.addEventListener('keydown', keydownHandler);\n return () => document.removeEventListener('keydown', keydownHandler);\n });\n\n function keydownHandler({ key }) {\n switch (key) {\n case 'Escape': onClose(); break;\n default:\n }\n }\n\n return !isVisible ? null : (\n <div className=\"modal\" onClick={onClose}>\n <div className=\"modal-dialog\" onClick={e => e.stopPropagation()}>\n <div className=\"modal-header\">\n <h3 className=\"modal-title\">{title}</h3>\n <span className=\"modal-close\" onClick={onClose}>×</span>\n </div>\n <div className=\"modal-body\">\n <div className=\"modal-content\">{ content }</div>\n </div>\n {footer && <div className=\"modal-footer\">{footer}</div>}\n </div>\n </div>\n )\n}\n```",
|
||||
"```jsx\n//Add the component to the render function\nfunction App() {\n const [ isModal, setModal] = React.useState(false);\n \n return (\n <React.Fragment>\n <button onClick={()=> setModal(true)}>Click Here</button>\n <Modal \n isVisible={ isModal }\n title= \"Modal Title\"\n content = {<p>Add your content here</p>}\n footer = {<button>Cancel</button>}\n onClose ={()=> setModal(false)}\n />\n </React.Fragment>\n )\n}\n\nReactDOM.render( <App/>, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"visual",
|
||||
"effect"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "MultiselectCheckbox.md",
|
||||
"title": "MultiselectCheckbox",
|
||||
"text": "Renders a checkbox list that uses a callback function to pass its selected value/values to the parent component.\n\n* Use `React.setState()` to create a `data` state variable and set its initial value equal to the `options` prop.\n* Create a function `toggle` that is used to toggle the `checked` to update the `data` state variable and call the `onChange` callback passed via the component's props.\n* Render a `<ul>` element and use `Array.prototype.map()` to map the `data` state variable to individual `<li>` elements with `<input>` elements as their children.\n* Each `<input>` element has the `type='checkbox'` attribute and is marked as `readOnly`, as its click events are handled by the parent `<li>` element's `onClick` handler.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nconst style = {\n listContainer: {\n listStyle: 'none',\n paddingLeft: 0\n },\n itemStyle: {\n cursor: 'pointer',\n padding: 5\n }\n};\n\nfunction MultiselectCheckbox({ options, onChange }) {\n const [data, setData] = React.useState(options);\n\n const toggle = item => {\n data.map((_, key) => {\n if (data[key].label === item.label) data[key].checked = !item.checked;\n });\n setData([...data]);\n onChange(data);\n };\n\n return (\n <ul style={style.listContainer}>\n {data.map(item => {\n return (\n <li key={item.label} style={style.itemStyle} onClick={() => toggle(item)}>\n <input readOnly type=\"checkbox\" checked={item.checked || false} />\n {item.label}\n </li>\n );\n })}\n </ul>\n );\n}\n```",
|
||||
"```jsx\nconst options = [{ label: 'Item One' }, { label: 'Item Two' }];\n\nReactDOM.render(\n <MultiselectCheckbox\n options={options}\n onChange={data => {\n console.log(data);\n }}\n />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 1,
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"array"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "PasswordRevealer.md",
|
||||
"title": "PasswordRevealer",
|
||||
"text": "Renders a password input field with a reveal button.\n\n* Use the `React.useState()` hook to create the `shown` state variable and set its value to `false`.\n* Use a`<div>` to wrap both the`<input>` and the `<button>` element that toggles the type of the input field between `\"text\"` and `\"password\"`.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction PasswordRevealer({ value }) {\n const [shown, setShown] = React.useState(false);\n\n return (\n <div>\n <input type={shown ? 'text' : 'password'} value={value} onChange={() => {}} />\n <button onClick={() => setShown(!shown)}>Show/Hide</button>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<PasswordRevealer />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input",
|
||||
"state"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Select.md",
|
||||
"title": "Select",
|
||||
"text": "Renders a `<select>` 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 `<select>` element.\n* Render a `<select>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the textarea to the parent.\n* Use destructuring on the `values` array to pass an array of `value` and `text` elements and the `selected` attribute to define the initial `value` of the `<select>` element.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Select({ values, callback, disabled = false, readonly = false, selected }) {\n return (\n <select\n disabled={disabled}\n readOnly={readonly}\n onChange={({ target: { value } }) => callback(value)}\n >\n {values.map(([value, text]) => (\n <option selected={selected === value} value={value}>\n {text}\n </option>\n ))}\n </select>\n );\n}\n```",
|
||||
"```jsx\nlet choices = [\n ['grapefruit', 'Grapefruit'],\n ['lime', 'Lime'],\n ['coconut', 'Coconut'],\n ['mango', 'Mango']\n];\nReactDOM.render(\n <Select values={choices} selected=\"lime\" callback={val => console.log(val)} />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Slider.md",
|
||||
"title": "Slider",
|
||||
"text": "Renders a slider 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 `<input>` element.\n* Render an `<input>` element of type `\"range\"` and the appropriate attributes, use the `callback` function in the `onChange` event to pass the value of the input to the parent.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Slider({ callback, disabled = false, readOnly = false }) {\n return (\n <input\n type=\"range\"\n disabled={disabled}\n readOnly={readOnly}\n onChange={({ target: { value } }) => callback(value)}\n />\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<Slider callback={val => console.log(val)} />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "StarRating.md",
|
||||
"title": "StarRating",
|
||||
"text": "Renders a star rating component.\n\n* Define a component, called `Star` that will render each individual star with the appropriate appearance, based on the parent component's state.\n* In the `StarRating` component, use the `React.useState()` hook to define the `rating` and `selection` state variables with the initial values of `props.rating` (or `0` if invalid or not supplied) and `0`.\n* Create a method, `hoverOver`, that updates `selected` and `rating` according to the provided `event`.\n* Create a `<div>` to wrap the `<Star>` components, which are created using `Array.prototype.map` on an array of 5 elements, created using `Array.from`, and handle the `onMouseLeave` event to set `selection` to `0`, the `onClick` event to set the `rating` and the `onMouseOver` event to set `selection` to the `star-id` attribute of the `event.target` respectively.\n* Finally, pass the appropriate values to each `<Star>` component (`starId` and `marked`).\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Star({ marked, starId }) {\n return (\n <span star-id={starId} style={{ color: '#ff9933' }} role=\"button\">\n {marked ? '\\u2605' : '\\u2606'}\n </span>\n );\n}\n\nfunction StarRating(props) {\n const [rating, setRating] = React.useState(typeof props.rating == 'number' ? props.rating : 0);\n const [selection, setSelection] = React.useState(0);\n const hoverOver = event => {\n let val = 0;\n if (event && event.target && event.target.getAttribute('star-id'))\n val = event.target.getAttribute('star-id');\n setSelection(val);\n };\n return (\n <div\n onMouseOut={() => hoverOver(null)}\n onClick={(event) => setRating(event.target.getAttribute('star-id') || this.state.rating)}\n onMouseOver={hoverOver}\n >\n {Array.from({ length: 5 }, (v, i) => (\n <Star\n starId={i + 1}\n key={`star_${i + 1} `}\n marked={selection ? selection >= i + 1 : rating >= i + 1}\n />\n ))}\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<StarRating />, document.getElementById('root'));\nReactDOM.render(<StarRating rating={2} />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"input",
|
||||
"state"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Tabs.md",
|
||||
"title": "Tabs",
|
||||
"text": "Renders a tabbed menu and view component.\n\n* Define a `TabItem` component, pass it to the `Tab` and remove unnecessary nodes expect for `TabItem` by identifying the function's name in `props.children`.\n* Use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.\n* Use `Array.prototype.map` on the collected nodes to render the `tab-menu` and `tab-view`.\n* Define `changeTab`, which will be executed when clicking a `<button>` from the `tab-menu`.\n* `changeTab` executes the passed callback, `onTabClick` and updates `bindIndex`, which in turn causes a re-render, evaluating the `style` and `className` of the `tab-view` items and `tab-menu` buttons according to their `index`.\n\n",
|
||||
"codeBlocks": [
|
||||
"```css\n.tab-menu > button {\n cursor: pointer;\n padding: 8px 16px;\n border: 0;\n border-bottom: 2px solid transparent;\n background: none;\n}\n.tab-menu > button.focus {\n border-bottom: 2px solid #007bef;\n}\n.tab-menu > button:hover {\n border-bottom: 2px solid #007bef;\n}\n```",
|
||||
"```jsx\nfunction TabItem(props) {\n return <div {...props} />;\n}\n\nfunction Tabs(props) {\n const [bindIndex, setBindIndex] = React.useState(props.defaultIndex);\n const changeTab = newIndex => {\n if (typeof props.onTabClick === 'function') props.onTabClick(newIndex);\n setBindIndex(newIndex);\n };\n const items = props.children.filter(item => item.type.name === 'TabItem');\n\n return (\n <div className=\"wrapper\">\n <div className=\"tab-menu\">\n {items.map(({ props: { index, label } }) => (\n <button onClick={() => changeTab(index)} className={bindIndex === index ? 'focus' : ''}>\n {label}\n </button>\n ))}\n </div>\n <div className=\"tab-view\">\n {items.map(({ props }) => (\n <div\n {...props}\n className=\"tab-view_item\"\n key={props.index}\n style={{ display: bindIndex === props.index ? 'block' : 'none' }}\n />\n ))}\n </div>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Tabs defaultIndex=\"1\" onTabClick={console.log}>\n <TabItem label=\"A\" index=\"1\">\n Lorem ipsum\n </TabItem>\n <TabItem label=\"B\" index=\"2\">\n Dolor sit amet\n </TabItem>\n </Tabs>,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 1,
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"children"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "TextArea.md",
|
||||
"title": "TextArea",
|
||||
"text": "Renders a `<textarea>` 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 `<textarea>` element.\n* Render a `<textarea>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the textarea to the parent.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction TextArea({\n callback,\n cols = 20,\n rows = 2,\n disabled = false,\n readOnly = false,\n placeholder = ''\n}) {\n return (\n <textarea\n cols={cols}\n rows={rows}\n disabled={disabled}\n readOnly={readOnly}\n placeholder={placeholder}\n onChange={({ target: { value } }) => callback(value)}\n />\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <TextArea placeholder=\"Insert some text here...\" callback={val => console.log(val)} />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Ticker.md",
|
||||
"title": "Ticker",
|
||||
"text": "Renders a ticker component.\n\n* Use the `React.useState()` hook to initialize the `ticker` state variable to `0`.\n* Define two methods, `tick` and `reset`, that will periodically increment `timer` based on `interval` and reset `interval` respectively.\n* Return a `<div>` with two `<button>` elements, each of which calls `tick` and `reset` respectively.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Ticker(props) {\n const [ticker, setTicker] = React.useState(0);\n let interval = null;\n\n const tick = () => {\n reset();\n interval = setInterval(() => {\n if (ticker < props.times) setTicker(ticker + 1);\n else clearInterval(interval);\n }, props.interval);\n };\n\n const reset = () => {\n setTicker(0);\n clearInterval(interval);\n };\n\n return (\n <div>\n <span style={{ fontSize: 100 }}>{this.state.ticker}</span>\n <button onClick={this.tick}>Tick!</button>\n <button onClick={this.reset}>Reset</button>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<Ticker times={5} interval={1000} />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 1,
|
||||
"tags": [
|
||||
"visual",
|
||||
"state"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Toggle.md",
|
||||
"title": "Toggle",
|
||||
"text": "Renders a toggle component.\n\n* Use the `React.useState()` to initialize the `isToggleOn` state variable to `false`.\n* Use an object, `style`, to hold the styles for individual components and their states.\n* Return a `<button>` that alters the component's `isToggledOn` when its `onClick` event is fired and determine the appearance of the content based on `isToggleOn`, applying the appropriate CSS rules from the `style` object.\n\n",
|
||||
"codeBlocks": [
|
||||
"```jsx\nfunction Toggle(props) {\n const [isToggleOn, setIsToggleOn] = React.useState(false);\n style = {\n on: {\n backgroundColor: 'green'\n },\n off: {\n backgroundColor: 'grey'\n }\n };\n\n return (\n <button onClick={() => setIsToggleOn(!isToggleOn)} style={isToggleOn ? style.on : style.off}>\n {isToggleOn ? 'ON' : 'OFF'}\n </button>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(<Toggle />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"visual",
|
||||
"state"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "Tooltip.md",
|
||||
"title": "Tooltip",
|
||||
"text": "Renders a tooltip component.\n\n* Use the `React.useState()` hook to create the `show` variable and initialize it to `false`.\n* Return a `<div>` element that contains the `<div>` that will be the tooltip and the `children` passed to the component.\n* Handle the `onMouseEnter` and `onMouseLeave` methods, by altering the value of the `show` variable.\n\n",
|
||||
"codeBlocks": [
|
||||
"```css\n.tooltip {\n position: relative;\n background: rgba(0, 0, 0, 0.7);\n color: white;\n visibility: hidden;\n padding: 5px;\n border-radius: 5px;\n}\n.tooltip-arrow {\n position: absolute;\n top: 100%;\n left: 50%;\n border-width: 5px;\n border-style: solid;\n border-color: rgba(0, 0, 0, 0.7) transparent transparent;\n}\n```",
|
||||
"```jsx\nfunction Tooltip({ children, text, ...rest }) {\n const [show, setShow] = React.useState(false);\n\n return (\n <div>\n <div className=\"tooltip\" style={show ? { visibility: 'visible' } : {}}>\n {text}\n <span className=\"tooltip-arrow\" />\n </div>\n <div {...rest} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n </div>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <Tooltip text=\"Simple tooltip\">\n <button>Hover me!</button>\n </Tooltip>,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 1,
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"children"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "TreeView.md",
|
||||
"title": "TreeView",
|
||||
"text": "Renders a tree view of a JSON object or array with collapsible content.\n\n* Use object destructuring to set defaults for certain props.\n* Use the value of the `toggled` prop to determine the initial state of the content (collapsed/expanded).\n* Use the `React.setState()` hook to create the `isToggled` state variable and give it the value of the `toggled` prop initially.\n* Return a `<div>` to wrap the contents of the component and the `<span>` element, used to alter the component's `isToggled` state.\n* Determine the appearance of the component, based on `isParentToggled`, `isToggled`, `name` and `Array.isArray()` on `data`.\n* For each child in `data`, determine if it is an object or array and recursively render a sub-tree.\n* Otherwise, render a `<p>` element with the appropriate style.\n\n",
|
||||
"codeBlocks": [
|
||||
"```css\n.tree-element {\n margin: 0;\n position: relative;\n}\n\ndiv.tree-element:before {\n content: '';\n position: absolute;\n top: 24px;\n left: 1px;\n height: calc(100% - 48px);\n border-left: 1px solid gray;\n}\n\n.toggler {\n position: absolute;\n top: 10px;\n left: 0px;\n width: 0;\n height: 0;\n border-top: 4px solid transparent;\n border-bottom: 4px solid transparent;\n border-left: 5px solid gray;\n cursor: pointer;\n}\n\n.toggler.closed {\n transform: rotate(90deg);\n}\n\n.collapsed {\n display: none;\n}\n```",
|
||||
"```jsx\nfunction TreeView({\n data,\n toggled = true,\n name = null,\n isLast = true,\n isChildElement = false,\n isParentToggled = true\n}) {\n const [isToggled, setIsToggled] = React.useState(toggled);\n\n return (\n <div\n style={{ marginLeft: isChildElement ? 16 : 4 + 'px' }}\n className={isParentToggled ? 'tree-element' : 'tree-element collapsed'}\n >\n <span\n className={isToggled ? 'toggler' : 'toggler closed'}\n onClick={() => setIsToggled(!isToggled)}\n />\n {name ? <strong> {name}: </strong> : <span> </span>}\n {Array.isArray(data) ? '[' : '{'}\n {!isToggled && '...'}\n {Object.keys(data).map((v, i, a) =>\n typeof data[v] == 'object' ? (\n <TreeView\n data={data[v]}\n isLast={i === a.length - 1}\n name={Array.isArray(data) ? null : v}\n isChildElement\n isParentToggled={isParentToggled && isToggled}\n />\n ) : (\n <p\n style={{ marginLeft: 16 + 'px' }}\n className={isToggled ? 'tree-element' : 'tree-element collapsed'}\n >\n {Array.isArray(data) ? '' : <strong>{v}: </strong>}\n {data[v]}\n {i === a.length - 1 ? '' : ','}\n </p>\n )\n )}\n {Array.isArray(data) ? ']' : '}'}\n {!isLast ? ',' : ''}\n </div>\n );\n}\n```",
|
||||
"```jsx\nlet data = {\n lorem: {\n ipsum: 'dolor sit',\n amet: {\n consectetur: 'adipiscing',\n elit: [\n 'duis',\n 'vitae',\n {\n semper: 'orci'\n },\n {\n est: 'sed ornare'\n },\n 'etiam',\n ['laoreet', 'tincidunt'],\n ['vestibulum', 'ante']\n ]\n },\n ipsum: 'primis'\n }\n};\nReactDOM.render(<TreeView data={data} name=\"data\" />, document.getElementById('root'));\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"object",
|
||||
"visual",
|
||||
"state",
|
||||
"recursion"
|
||||
],
|
||||
"notes": []
|
||||
}
|
||||
]
|
||||
19499
package-lock.json
generated
19499
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -6,15 +6,53 @@
|
||||
"url": "git+https://github.com/30-seconds/30-seconds-of-react.git"
|
||||
},
|
||||
"scripts": {
|
||||
"extractor": "node ./scripts/extract.js",
|
||||
"builder": "node ./scripts/build.js",
|
||||
"webber": "gatsby build",
|
||||
"webber:dev": "gatsby develop",
|
||||
"webber:serve": "gatsby serve",
|
||||
"extractor": "node ./scripts/extract.js",
|
||||
"linter": "prettier **/*.{js,json,css,md} --write"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chalk": "^2.4.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"@babel/plugin-proposal-class-properties": "^7.5.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/preset-env": "^7.5.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
"front-matter": "^3.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gatsby": "^2.12.0",
|
||||
"gatsby-image": "^2.2.6",
|
||||
"gatsby-plugin-google-analytics": "^2.1.6",
|
||||
"gatsby-plugin-manifest": "^2.2.3",
|
||||
"gatsby-plugin-netlify": "^2.1.3",
|
||||
"gatsby-plugin-offline": "^2.2.4",
|
||||
"gatsby-plugin-page-creator": "^2.1.5",
|
||||
"gatsby-plugin-react-helmet": "^3.1.2",
|
||||
"gatsby-plugin-sass": "^2.1.3",
|
||||
"gatsby-plugin-sharp": "^2.2.7",
|
||||
"gatsby-plugin-transition-link": "^1.12.4",
|
||||
"gatsby-remark-copy-linked-files": "^2.1.3",
|
||||
"gatsby-remark-images": "^3.1.6",
|
||||
"gatsby-remark-prismjs": "^3.3.3",
|
||||
"gatsby-source-filesystem": "^2.1.5",
|
||||
"gatsby-transformer-json": "^2.2.2",
|
||||
"gatsby-transformer-remark": "^2.6.6",
|
||||
"gatsby-transformer-sharp": "^2.2.3",
|
||||
"gsap": "^2.1.3",
|
||||
"kleur": "^3.0.3",
|
||||
"markdown-builder": "^0.9.0",
|
||||
"prettier": "^1.14.3"
|
||||
"node-sass": "^4.12.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prismjs": "^1.16.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.6",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-css-transition-replace": "^3.0.3",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-redux": "^7.1.0",
|
||||
"redux": "^4.0.4"
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
|
||||
182
scripts/build.js
182
scripts/build.js
@ -1,97 +1,141 @@
|
||||
/*
|
||||
This is the builder script that generates the README file.
|
||||
Run using `npm run builder`.
|
||||
*/
|
||||
// Load modules
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const util = require('./util.js');
|
||||
const { green, red } = require('kleur');
|
||||
const util = require('./util');
|
||||
const markdown = require('markdown-builder');
|
||||
const snippets = require('../data/snippet_data.json');
|
||||
|
||||
const { headers, misc, lists } = markdown;
|
||||
const TAG_NAMES = [...new Set(snippets.reduce((acc, v) => [...acc, v.tags[0]], []))].sort((a, b) =>
|
||||
a.localeCompare(b)
|
||||
);
|
||||
console.log(TAG_NAMES);
|
||||
const config = require('../config');
|
||||
|
||||
const STATIC_PARTS_PATH = './static-parts';
|
||||
// Paths (relative to package.json)
|
||||
const SNIPPETS_PATH = `./${config.snippetPath}`;
|
||||
const STATIC_PARTS_PATH = `./${config.staticPartsPath}`;
|
||||
|
||||
let startPart = '';
|
||||
let endPart = '';
|
||||
let output = '';
|
||||
// Terminate if parent commit is a Travis build
|
||||
if (
|
||||
util.isTravisCI() &&
|
||||
/^Travis build: \d+/g.test(process.env['TRAVIS_COMMIT_MESSAGE'])
|
||||
) {
|
||||
console.log(
|
||||
`${green(
|
||||
'NOBUILD',
|
||||
)} README build terminated, parent commit is a Travis build!`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const detailsTOC = (title, snippetsArray) =>
|
||||
`\n${misc
|
||||
.collapsible(
|
||||
title,
|
||||
lists
|
||||
.ul(snippetsArray, snippet =>
|
||||
misc.link(
|
||||
snippet.title
|
||||
.replace('\n', '')
|
||||
.split('```')[0]
|
||||
.trim(),
|
||||
misc.anchor(
|
||||
snippet.title
|
||||
.replace('\n', '')
|
||||
.split('```')[0]
|
||||
.trim()
|
||||
)
|
||||
)
|
||||
)
|
||||
.trim()
|
||||
)
|
||||
.trim()}\n\n`;
|
||||
// Setup everything
|
||||
let snippets = {},
|
||||
snippetsArray = [],
|
||||
startPart = '',
|
||||
endPart = '',
|
||||
output = '';
|
||||
const EMOJIS = {};
|
||||
|
||||
console.time('Builder');
|
||||
|
||||
// Synchronously read all snippets from snippets folder and sort them as necessary (case-insensitive)
|
||||
snippets = util.readSnippets(SNIPPETS_PATH);
|
||||
snippetsArray = Object.keys(snippets).reduce((acc, key) => {
|
||||
acc.push(snippets[key]);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// Load static parts for the README file
|
||||
try {
|
||||
startPart = fs.readFileSync(path.join(STATIC_PARTS_PATH, 'README-start.md'), 'utf8');
|
||||
endPart = fs.readFileSync(path.join(STATIC_PARTS_PATH, 'README-end.md'), 'utf8');
|
||||
startPart = fs.readFileSync(
|
||||
path.join(STATIC_PARTS_PATH, 'README-start.md'),
|
||||
'utf8',
|
||||
);
|
||||
endPart = fs.readFileSync(
|
||||
path.join(STATIC_PARTS_PATH, 'README-end.md'),
|
||||
'utf8',
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(`${chalk.red('ERROR!')} During static part loading: ${err}`);
|
||||
console.log(`${red('ERROR!')} During static part loading: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create the output for the README file
|
||||
try {
|
||||
// add static part for start
|
||||
const tags = util.prepTaggedData(
|
||||
Object.keys(snippets).reduce((acc, key) => {
|
||||
acc[key] = snippets[key].attributes.tags;
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
output += `${startPart}\n`;
|
||||
|
||||
const snippetsInTag = {};
|
||||
// Loop over tags and snippets to create the table of contents
|
||||
for (const tag of tags) {
|
||||
const capitalizedTag = util.capitalize(tag, true);
|
||||
const taggedSnippets = snippetsArray.filter(
|
||||
snippet => snippet.attributes.tags[0] === tag,
|
||||
);
|
||||
output += headers.h3((EMOJIS[tag] || '') + ' ' + capitalizedTag).trim();
|
||||
|
||||
TAG_NAMES.forEach(tag => (snippetsInTag[tag] = snippets.filter(v => v.tags[0] == tag)));
|
||||
|
||||
// write Table of Contents
|
||||
TAG_NAMES.forEach(tag => {
|
||||
const taggedSnippets = snippetsInTag[tag];
|
||||
output += headers.h3(util.capitalize(tag));
|
||||
output += detailsTOC('View contents', taggedSnippets);
|
||||
});
|
||||
|
||||
// delimeter after TOC
|
||||
output += misc.hr();
|
||||
|
||||
// write actual snippets
|
||||
TAG_NAMES.forEach(tag => {
|
||||
output += headers.h2(util.capitalize(tag));
|
||||
const taggedSnippets = snippetsInTag[tag];
|
||||
taggedSnippets.forEach(snippet => {
|
||||
output += headers.h3(snippet.title).trim();
|
||||
output += `\n\n${snippet.text}${snippet.codeBlocks.slice(0, -1).join('\n\n')}`;
|
||||
if (snippet.notes && snippet.notes.length) {
|
||||
output += headers.h4('Notes');
|
||||
output += `\n${snippet.notes}`;
|
||||
output +=
|
||||
misc.collapsible(
|
||||
'View contents',
|
||||
lists.ul(taggedSnippets, snippet =>
|
||||
misc.link(
|
||||
`\`${snippet.title}\``,
|
||||
`${misc.anchor(snippet.title)}${
|
||||
snippet.attributes.tags.includes('advanced') ? '-' : ''
|
||||
}`,
|
||||
),
|
||||
),
|
||||
) + '\n';
|
||||
}
|
||||
output += misc.collapsible('Examples', snippet.codeBlocks.slice(-1));
|
||||
output += `\n<br>${misc.link('⬆ Back to top', misc.anchor('Table of Contents'))}\n\n`;
|
||||
});
|
||||
});
|
||||
|
||||
// add static part for end
|
||||
for (const tag of tags) {
|
||||
const capitalizedTag = util.capitalize(tag, true);
|
||||
const taggedSnippets = snippetsArray.filter(
|
||||
snippet => snippet.attributes.tags[0] === tag,
|
||||
);
|
||||
|
||||
output +=
|
||||
misc.hr() + headers.h2((EMOJIS[tag] || '') + ' ' + capitalizedTag) + '\n';
|
||||
|
||||
for (let snippet of taggedSnippets) {
|
||||
if (snippet.attributes.tags.includes('advanced'))
|
||||
output +=
|
||||
headers.h3(
|
||||
snippet.title + ' ' + misc.image('advanced', '/advanced.svg'),
|
||||
) + '\n';
|
||||
else output += headers.h3(snippet.title) + '\n';
|
||||
|
||||
output += snippet.attributes.text;
|
||||
|
||||
if (snippet.attributes.codeBlocks.style !== '')
|
||||
output += `\`\`\`${config.optionalLanguage}\n${snippet.attributes.codeBlocks.style}\n\`\`\`\n\n`;
|
||||
|
||||
|
||||
output += `\`\`\`${config.language}\n${snippet.attributes.codeBlocks.code}\n\`\`\``;
|
||||
|
||||
output += misc.collapsible(
|
||||
'Examples',
|
||||
`\`\`\`${config.language}\n${snippet.attributes.codeBlocks.example}\n\`\`\``,
|
||||
);
|
||||
|
||||
output +=
|
||||
'\n<br>' + misc.link('⬆ Back to top', misc.anchor('Contents')) + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Add the ending static part
|
||||
output += `\n${endPart}\n`;
|
||||
|
||||
// Write to the README file
|
||||
fs.writeFileSync('README.md', output);
|
||||
} catch (err) {
|
||||
console.log(`${chalk.red('ERROR!')} During README generation: ${err}`);
|
||||
console.log(`${red('ERROR!')} During README generation: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`${chalk.green('SUCCESS!')} README file generated!`);
|
||||
console.log(`${green('SUCCESS!')} README file generated!`);
|
||||
console.timeEnd('Builder');
|
||||
|
||||
@ -1,42 +1,80 @@
|
||||
/*
|
||||
This is the extractor script that generates the snippets.json file.
|
||||
Run using `npm run extractor`.
|
||||
*/
|
||||
// Load modules
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const {
|
||||
attempt,
|
||||
readSnippets,
|
||||
getCodeBlocks,
|
||||
getSection,
|
||||
getTitle,
|
||||
getTextualContent
|
||||
} = require('./util');
|
||||
const { green } = require('kleur');
|
||||
const util = require('./util');
|
||||
const config = require('../config');
|
||||
|
||||
// Paths (relative to package.json)
|
||||
const SNIPPETS_PATH = `./${config.snippetPath}`;
|
||||
const OUTPUT_PATH = `./${config.snippetDataPath}`;
|
||||
|
||||
// Check if running on Travis, only build for cron jobs and custom builds
|
||||
if (
|
||||
util.isTravisCI() &&
|
||||
process.env['TRAVIS_EVENT_TYPE'] !== 'cron' &&
|
||||
process.env['TRAVIS_EVENT_TYPE'] !== 'api'
|
||||
) {
|
||||
console.log(
|
||||
`${green(
|
||||
'NOBUILD',
|
||||
)} snippet extraction terminated, not a cron or api build!`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Setup everything
|
||||
let snippets = {},
|
||||
snippetsArray = [];
|
||||
console.time('Extractor');
|
||||
|
||||
attempt('snippet_data.json generation', () => {
|
||||
const output = Object.entries(readSnippets()).map(([name, contents]) => {
|
||||
const title = getTitle(contents);
|
||||
const text = getTextualContent(contents);
|
||||
const codeBlocks = getCodeBlocks(contents);
|
||||
const notes = getSection('#### Notes', contents, false)
|
||||
.split('\n')
|
||||
.map(v => v.replace(/[*-] /g, ''))
|
||||
.filter(v => v.trim() !== '');
|
||||
// Synchronously read all snippets from snippets folder and sort them as necessary (case-insensitive)
|
||||
snippets = util.readSnippets(SNIPPETS_PATH);
|
||||
snippetsArray = Object.keys(snippets).reduce((acc, key) => {
|
||||
acc.push(snippets[key]);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
name,
|
||||
title,
|
||||
text,
|
||||
codeBlocks,
|
||||
expertise: parseInt((contents.match(/<!--\s*expertise:\s*\(*(.+)\)*/) || [])[1], 10),
|
||||
tags: (contents.match(/<!--\s*tags:\s*\(*(.+)\)*\s*-->/) || [])[1]
|
||||
.split(',')
|
||||
.map(v => v.trim()),
|
||||
notes
|
||||
};
|
||||
});
|
||||
|
||||
fs.writeFileSync('./data/snippet_data.json', JSON.stringify(output, null, 2));
|
||||
});
|
||||
|
||||
console.log(`${chalk.green('SUCCESS!')} snippet_data.json file generated!`);
|
||||
const completeData = {
|
||||
data: [...snippetsArray],
|
||||
meta: {
|
||||
specification: 'http://jsonapi.org/format/',
|
||||
type: 'snippetArray',
|
||||
},
|
||||
};
|
||||
let listingData = {
|
||||
data: completeData.data.map(v => ({
|
||||
id: v.id,
|
||||
type: 'snippetListing',
|
||||
title: v.title,
|
||||
attributes: {
|
||||
text: v.attributes.text,
|
||||
tags: v.attributes.tags,
|
||||
},
|
||||
meta: {
|
||||
hash: v.meta.hash,
|
||||
},
|
||||
})),
|
||||
meta: {
|
||||
specification: 'http://jsonapi.org/format/',
|
||||
type: 'snippetListingArray',
|
||||
},
|
||||
};
|
||||
// Write files
|
||||
fs.writeFileSync(
|
||||
path.join(OUTPUT_PATH, 'snippets.json'),
|
||||
JSON.stringify(completeData, null, 2),
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(OUTPUT_PATH, 'snippetList.json'),
|
||||
JSON.stringify(listingData, null, 2),
|
||||
);
|
||||
// Display messages and time
|
||||
console.log(
|
||||
`${green('SUCCESS!')} snippets.json and snippetList.json files generated!`,
|
||||
);
|
||||
console.timeEnd('Extractor');
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const SNIPPETS_PATH = './snippets';
|
||||
|
||||
const attempt = (task, cb) => {
|
||||
try {
|
||||
return cb();
|
||||
} catch (e) {
|
||||
console.log(`${chalk.red('ERROR!')} During ${task}: ${e}`);
|
||||
process.exit(1);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const capitalize = ([first, ...rest], lowerRest = false) =>
|
||||
first.toUpperCase() + (lowerRest ? rest.join('').toLowerCase() : rest.join(''));
|
||||
|
||||
const readSnippets = () =>
|
||||
attempt('read snippets', () =>
|
||||
fs
|
||||
.readdirSync(SNIPPETS_PATH)
|
||||
.sort((a, b) => (a.toLowerCase() < b.toLowerCase() ? -1 : 1))
|
||||
.reduce((acc, name) => {
|
||||
acc[name] = fs.readFileSync(path.join(SNIPPETS_PATH, name), 'utf8').replace(/\r\n/g, '\n');
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const getCodeBlocks = str => {
|
||||
const regex = /```[.\S\s]*?```/g;
|
||||
const results = [];
|
||||
let m = null;
|
||||
while ((m = regex.exec(str)) !== null) {
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex += 1;
|
||||
}
|
||||
m.forEach(match => results.push(match));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
const getSection = (searchString, contents, includeSubsections = true) => {
|
||||
const indexOfSearch = contents.indexOf(searchString);
|
||||
if (indexOfSearch < 0) return '';
|
||||
|
||||
let endSearch = '\\n#';
|
||||
if (includeSubsections) {
|
||||
let i;
|
||||
for (i = 0; searchString[i] === '#' && i < searchString.length; i++);
|
||||
|
||||
if (i > 0) {
|
||||
endSearch += `{${i - 1},${i}}[^#]`;
|
||||
}
|
||||
}
|
||||
const endRegex = new RegExp(endSearch);
|
||||
|
||||
const sliceStart = indexOfSearch + searchString.length + 1;
|
||||
const endIndex = contents.slice(sliceStart).search(endRegex);
|
||||
const sliceEnd = endIndex === -1 ? undefined : endIndex + sliceStart;
|
||||
|
||||
return contents.slice(sliceStart, sliceEnd).trim();
|
||||
};
|
||||
|
||||
const getTextualContent = str => {
|
||||
const regex = /###.*\n*([\s\S]*?)```/g;
|
||||
const results = [];
|
||||
let m = null;
|
||||
while ((m = regex.exec(str)) !== null) {
|
||||
if (m.index === regex.lastIndex) regex.lastIndex += 1;
|
||||
|
||||
m.forEach((match, groupIndex) => {
|
||||
results.push(match);
|
||||
});
|
||||
}
|
||||
return results[1];
|
||||
};
|
||||
|
||||
const getTitle = contents => contents.split('\n')[0].replace(/^#+\s+/g, '');
|
||||
|
||||
module.exports = {
|
||||
attempt,
|
||||
readSnippets,
|
||||
SNIPPETS_PATH,
|
||||
capitalize,
|
||||
getTextualContent,
|
||||
getCodeBlocks,
|
||||
getSection,
|
||||
getTitle
|
||||
};
|
||||
12
scripts/util/environmentCheck.js
Normal file
12
scripts/util/environmentCheck.js
Normal file
@ -0,0 +1,12 @@
|
||||
// Checks if current environment is Travis CI, Cron builds, API builds
|
||||
const isTravisCI = () => 'TRAVIS' in process.env && 'CI' in process.env;
|
||||
const isTravisCronOrAPI = () =>
|
||||
process.env['TRAVIS_EVENT_TYPE'] === 'cron' ||
|
||||
process.env['TRAVIS_EVENT_TYPE'] === 'api';
|
||||
const isNotTravisCronOrAPI = () => !isTravisCronOrAPI();
|
||||
|
||||
module.exports = {
|
||||
isTravisCI,
|
||||
isTravisCronOrAPI,
|
||||
isNotTravisCronOrAPI,
|
||||
};
|
||||
60
scripts/util/helpers.js
Normal file
60
scripts/util/helpers.js
Normal file
@ -0,0 +1,60 @@
|
||||
const config = require('../../config');
|
||||
|
||||
const getMarkDownAnchor = paragraphTitle =>
|
||||
paragraphTitle
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\- ]+/g, '')
|
||||
.replace(/\s/g, '-')
|
||||
.replace(/\-+$/, '');
|
||||
// Creates an object from pairs
|
||||
const objectFromPairs = arr => arr.reduce((a, v) => ((a[v[0]] = v[1]), a), {});
|
||||
// Optimizes nodes in an HTML document
|
||||
const optimizeNodes = (data, regexp, replacer) => {
|
||||
let count = 0;
|
||||
let output = data;
|
||||
do {
|
||||
output = output.replace(regexp, replacer);
|
||||
count = 0;
|
||||
while (regexp.exec(output) !== null) ++count;
|
||||
} while (count > 0);
|
||||
return output;
|
||||
};
|
||||
// Capitalizes the first letter of a string
|
||||
const capitalize = (str, lowerRest = false) =>
|
||||
str.slice(0, 1).toUpperCase() +
|
||||
(lowerRest ? str.slice(1).toLowerCase() : str.slice(1));
|
||||
const prepTaggedData = tagDbData =>
|
||||
[...new Set(Object.entries(tagDbData).map(t => t[1][0]))]
|
||||
.filter(v => v)
|
||||
.sort((a, b) =>
|
||||
capitalize(a, true) === 'Uncategorized'
|
||||
? 1
|
||||
: capitalize(b, true) === 'Uncategorized'
|
||||
? -1
|
||||
: a.localeCompare(b),
|
||||
);
|
||||
const makeExamples = data => {
|
||||
data =
|
||||
data.slice(0, data.lastIndexOf(`\`\`\`${config.language}`)).trim() +
|
||||
misc.collapsible(
|
||||
'Examples',
|
||||
data.slice(
|
||||
data.lastIndexOf(`\`\`\`${config.language}`),
|
||||
data.lastIndexOf('```'),
|
||||
) + data.slice(data.lastIndexOf('```')),
|
||||
);
|
||||
return `${data}\n<br>${misc.link(
|
||||
'⬆ Back to top',
|
||||
misc.anchor('Contents'),
|
||||
)}\n\n`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getMarkDownAnchor,
|
||||
objectFromPairs,
|
||||
optimizeNodes,
|
||||
capitalize,
|
||||
prepTaggedData,
|
||||
makeExamples,
|
||||
};
|
||||
37
scripts/util/index.js
Normal file
37
scripts/util/index.js
Normal file
@ -0,0 +1,37 @@
|
||||
const {
|
||||
isTravisCI,
|
||||
isTravisCronOrAPI,
|
||||
isNotTravisCronOrAPI,
|
||||
} = require('./environmentCheck');
|
||||
const {
|
||||
getMarkDownAnchor,
|
||||
objectFromPairs,
|
||||
optimizeNodes,
|
||||
capitalize,
|
||||
prepTaggedData,
|
||||
makeExamples,
|
||||
} = require('./helpers');
|
||||
const {
|
||||
getFilesInDir,
|
||||
hashData,
|
||||
getCodeBlocks,
|
||||
getTextualContent,
|
||||
readSnippets,
|
||||
} = require('./snippetParser');
|
||||
|
||||
module.exports = {
|
||||
isTravisCI,
|
||||
isTravisCronOrAPI,
|
||||
isNotTravisCronOrAPI,
|
||||
getMarkDownAnchor,
|
||||
objectFromPairs,
|
||||
optimizeNodes,
|
||||
capitalize,
|
||||
prepTaggedData,
|
||||
makeExamples,
|
||||
getFilesInDir,
|
||||
hashData,
|
||||
getCodeBlocks,
|
||||
getTextualContent,
|
||||
readSnippets,
|
||||
};
|
||||
129
scripts/util/snippetParser.js
Normal file
129
scripts/util/snippetParser.js
Normal file
@ -0,0 +1,129 @@
|
||||
const fs = require('fs-extra'),
|
||||
path = require('path'),
|
||||
{ red } = require('kleur'),
|
||||
crypto = require('crypto'),
|
||||
frontmatter = require('front-matter');
|
||||
const config = require('../../config');
|
||||
|
||||
// Reade all files in a directory
|
||||
const getFilesInDir = (directoryPath, withPath, exclude = null) => {
|
||||
try {
|
||||
let directoryFilenames = fs.readdirSync(directoryPath);
|
||||
directoryFilenames.sort((a, b) => {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (withPath) {
|
||||
// a hacky way to do conditional array.map
|
||||
return directoryFilenames.reduce((fileNames, fileName) => {
|
||||
if (
|
||||
exclude == null ||
|
||||
!exclude.some(toExclude => fileName === toExclude)
|
||||
)
|
||||
fileNames.push(`${directoryPath}/${fileName}`);
|
||||
return fileNames;
|
||||
}, []);
|
||||
}
|
||||
return directoryFilenames.filter(v => v !== 'README.md');
|
||||
} catch (err) {
|
||||
console.log(`${red('ERROR!')} During snippet loading: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
// Creates a hash for a value using the SHA-256 algorithm.
|
||||
const hashData = val =>
|
||||
crypto
|
||||
.createHash('sha256')
|
||||
.update(val)
|
||||
.digest('hex');
|
||||
// Gets the code blocks for a snippet file.
|
||||
const getCodeBlocks = str => {
|
||||
const regex = /```[.\S\s]*?```/g;
|
||||
let results = [];
|
||||
let m = null;
|
||||
while ((m = regex.exec(str)) !== null) {
|
||||
if (m.index === regex.lastIndex) regex.lastIndex += 1;
|
||||
|
||||
m.forEach((match, groupIndex) => {
|
||||
results.push(match);
|
||||
});
|
||||
}
|
||||
const replacer = new RegExp(
|
||||
`\`\`\`${config.language}([\\s\\S]*?)\`\`\``,
|
||||
'g',
|
||||
);
|
||||
const optionalReplacer = new RegExp(
|
||||
`\`\`\`${config.optionalLanguage}([\\s\\S]*?)\`\`\``,
|
||||
'g',
|
||||
);
|
||||
results = results.map(v => v.replace(replacer, '$1').replace(optionalReplacer, '$1').trim());
|
||||
if(results.length > 2)
|
||||
return {
|
||||
style: results[0],
|
||||
code: results[1],
|
||||
example: results[2],
|
||||
};
|
||||
return {
|
||||
style: '',
|
||||
code: results[0],
|
||||
example: results[1],
|
||||
};
|
||||
};
|
||||
// Gets the textual content for a snippet file.
|
||||
const getTextualContent = str => {
|
||||
const regex = /([\s\S]*?)```/g;
|
||||
const results = [];
|
||||
let m = null;
|
||||
while ((m = regex.exec(str)) !== null) {
|
||||
if (m.index === regex.lastIndex) regex.lastIndex += 1;
|
||||
|
||||
m.forEach((match, groupIndex) => {
|
||||
results.push(match);
|
||||
});
|
||||
}
|
||||
return results[1].replace(/\r\n/g, '\n');
|
||||
};
|
||||
|
||||
// Synchronously read all snippets and sort them as necessary (case-insensitive)
|
||||
const readSnippets = snippetsPath => {
|
||||
const snippetFilenames = getFilesInDir(snippetsPath, false);
|
||||
|
||||
let snippets = {};
|
||||
try {
|
||||
for (let snippet of snippetFilenames) {
|
||||
let data = frontmatter(
|
||||
fs.readFileSync(path.join(snippetsPath, snippet), 'utf8'),
|
||||
);
|
||||
snippets[snippet] = {
|
||||
id: snippet.slice(0, -3),
|
||||
title: data.attributes.title,
|
||||
type: 'snippet',
|
||||
attributes: {
|
||||
fileName: snippet,
|
||||
text: getTextualContent(data.body),
|
||||
codeBlocks: getCodeBlocks(data.body),
|
||||
tags: data.attributes.tags.split(',').map(t => t.trim()),
|
||||
},
|
||||
meta: {
|
||||
hash: hashData(data.body),
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`${red('ERROR!')} During snippet loading: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
return snippets;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getFilesInDir,
|
||||
hashData,
|
||||
getCodeBlocks,
|
||||
getTextualContent,
|
||||
readSnippets,
|
||||
};
|
||||
437
snippet_data/snippetList.json
Normal file
437
snippet_data/snippetList.json
Normal file
@ -0,0 +1,437 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "Accordion",
|
||||
"type": "snippetListing",
|
||||
"title": "Accordion",
|
||||
"attributes": {
|
||||
"text": "Renders an accordion menu with multiple collapsible content components.\n\n* Define an `AccordionItem` component, pass it to the `Accordion` and remove unnecessary nodes expect for `AccordionItem` by identifying the function's name in `props.children`.\n* Each `AccordionItem` component renders a `<button>` that is used to update the `Accordion` via the `props.handleClick` callback and the content of the component, passed down via `props.children`, while its appearance is determined by `props.isCollapsed` and based on `style`.\n* In the `Accordion` component, use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.\n* Use `Array.prototype.map` on the collected nodes to render the individual collapsiple elements.\n* Define `changeItem`, which will be executed when clicking an `AccordionItem`'s `<button>`.\n`changeItem` executes the passed callback, `onItemClick` and updates `bindIndex` based on the clicked element.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "7787feea6f6ad8bb1b1a73cf2938673d41fbc0dc8b29d5ebbbafe572784d963e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AutoLink",
|
||||
"type": "snippetListing",
|
||||
"title": "AutoLink",
|
||||
"attributes": {
|
||||
"text": "Renders a string as plaintext, with URLs converted to appropriate `<a>` elements.\n\n* Use `String.prototype.split()` and `String.prototype.match()` with a regular expression to find URLs in a string.\n* Return a `<React.Fragment>` with matched URLs rendered as `<a>` elements, dealing with missing protocol prefixes if necessary, and the rest of the string rendered as plaintext.\n\n",
|
||||
"tags": [
|
||||
"string",
|
||||
"fragment",
|
||||
"regexp",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "4ca8f86fe197110cbdc91241f1c93ccb31ebe23f9b8eabe00f23c31f1939db1f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Carousel",
|
||||
"type": "snippetListing",
|
||||
"title": "Carousel",
|
||||
"attributes": {
|
||||
"text": "Renders a carousel component.\n\n* Use the `React.setState()` hook to create the `active` state variable and give it a value of `0` (index of the first item).\n* Use an object, `style`, to hold the styles for the individual components.\n* Use the `React.setEffect()` hook to update the value of `active` to the index of the next item, using `setTimeout`.\n* Destructure `props`, compute if visibility style should be set to `visible` or not for each carousel item while mapping over and applying the combined style to the carousel item component accordingly.\n* Render the carousel items using `React.cloneElement()` and pass down rest `props` along with the computed styles.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state",
|
||||
"effect",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "c585316c31ab75c906dd8a9023474bfd68eb37dde735039bcca6067a2acd2894"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ClickOutAndInside",
|
||||
"type": "snippetListing",
|
||||
"title": "ClickInside and ClickOutside",
|
||||
"attributes": {
|
||||
"text": "Two handy hooks to handle the click outside and inside event on the wrapped component.\n\n* Create customized hooks that take in a `ref` component(node) and a `callback` function to hanlde the customized `click` event\n* Use the `React.useEffect()` hook to append and clean up the `click` event.\n* Use the `React.useRef()` hook to create a `ref` for your click component and pass it to `useClickInside` and `useClickOutside` hooks.\n\n",
|
||||
"tags": [
|
||||
"hooks",
|
||||
"effect",
|
||||
"event",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "773cb2ce0e28ff705f350ff82398836268bce65b32a2b93664bd535fbeed61e8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Collapse",
|
||||
"type": "snippetListing",
|
||||
"title": "Collapse",
|
||||
"attributes": {
|
||||
"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 `<div>` to wrap both the `<button>` 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",
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "0fb184a53478714a3eb9d9906f7661d5fa416db6bdb2b2b819add2ffb38f2a22"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CountDown",
|
||||
"type": "snippetListing",
|
||||
"title": "CountDown",
|
||||
"attributes": {
|
||||
"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 `<div>` to wrap a `<p>` element with the textual representation of the components `time` state variable, as well as two `<button>` 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",
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "cab16a22ce97e7a90a4ca0717b62a185bcbcd37fa55dc6983c79ea5857f76aaf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "DataList",
|
||||
"type": "snippetListing",
|
||||
"title": "DataList",
|
||||
"attributes": {
|
||||
"text": "Renders a list of elements from an array of primitives.\n\n* Use the value of the `isOrdered` prop to conditionally render a `<ol>` or `<ul>` list.\n* Use `Array.prototype.map` to render every item in `data` as a `<li>` element, give it a `key` produced from the concatenation of the its index and value.\n* Omit the `isOrdered` prop to render a `<ul>` list by default.\n\n",
|
||||
"tags": [
|
||||
"array",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "0280f7d4991a3145a1263342ef5ffa2e845c24c0793a2a06d40ed29a450b1039"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "DataTable",
|
||||
"type": "snippetListing",
|
||||
"title": "DataTable",
|
||||
"attributes": {
|
||||
"text": "Renders a table with rows dynamically created from an array of primitives.\n\n* Render a `<table>` element with two columns (`ID` and `Value`).\n* Use `Array.prototype.map` to render every item in `data` as a `<tr>` element, consisting of its index and value, give it a `key` produced from the concatenation of the two.\n\n",
|
||||
"tags": [
|
||||
"array",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "6da7e808336fe96d699685abb7d93735693f6e8dfba4a84262b9e02a624da617"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "FileDrop",
|
||||
"type": "snippetListing",
|
||||
"title": "FileDrop",
|
||||
"attributes": {
|
||||
"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 `<div>` and use `drag` and `filename` to determine its contents and style.\n* Finally, bind the `ref` of the created `<div>` to `dropRef`.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"input",
|
||||
"state",
|
||||
"effect",
|
||||
"event",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "79742466c371960a0134df6158931d6468a2767fc0d21e20b6709bb46c737941"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "LimitedTextarea",
|
||||
"type": "snippetListing",
|
||||
"title": "LimitedTextarea",
|
||||
"attributes": {
|
||||
"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`<div>` to wrap both the`<textarea>` and the `<p>` element that displays the character count and bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect",
|
||||
"event",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "a9aa0a8f818ba02a861fca0f91a9a164dedcf7683b5d38391003e1864a79e627"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "LimitedWordTextarea",
|
||||
"type": "snippetListing",
|
||||
"title": "LimitedWordTextarea",
|
||||
"attributes": {
|
||||
"text": "Renders a textarea component with a word limit.\n\n* Use the `React.useState()` hook to create the `content` and `wordCount` state variables and set their values to `value` and `0` respectively.\n* Create a method `setFormattedContent`, which uses `String.prototype.split(' ')` to turn the input into an array of words and check if the result of applying `Array.prototype.filter(Boolean)` has a `length` longer than `limit`.\n* If the afforementioned `length` exceeds the `limit`, trim the input, otherwise return the raw input, updating `content` and `wordCount` accordingly in both cases.\n* Use the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.\n* Use a`<div>` to wrap both the`<textarea>` and the `<p>` element that displays the character count and bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect",
|
||||
"event",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "88c10a294c994103d88ab6793cbf290b707d93f6cd0cf9272186e148c0a739bf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Mailto",
|
||||
"type": "snippetListing",
|
||||
"title": "Mailto",
|
||||
"attributes": {
|
||||
"text": "Renders a link formatted to send an email.\n\n* Destructure the component's props, use `email`, `subject` and `body` to create a `<a>` element with an appropriate `href` attribute.\n* Render the link with `props.children` as its content.\n\n",
|
||||
"tags": [
|
||||
"viual",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "1af32eb31db03931a3d0ec99311b0404f4f1b34a597b6fdf8a000b5d3348e09f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "MappedTable",
|
||||
"type": "snippetListing",
|
||||
"title": "MappedTable",
|
||||
"attributes": {
|
||||
"text": "Renders a table with rows dynamically created from an array of objects and a list of property names.\n\n* Use `Object.keys()`, `Array.prototype.filter()`, `Array.prototype.includes()` and `Array.prototype.reduce()` to produce a `filteredData` array, containing all objects with the keys specified in `propertyNames`.\n* Render a `<table>` element with a set of columns equal to the amount of values in `propertyNames`.\n* Use `Array.prototype.map` to render each value in the `propertyNames` array as a `<th>` element.\n* Use `Array.prototype.map` to render each object in the `filteredData` array as a `<tr>` element, containing a `<td>` for each key in the object.\n\n*This component does not work with nested objects and will break if there are nested objects inside any of the properties specified in `propertyNames`*\n\n",
|
||||
"tags": [
|
||||
"array",
|
||||
"object",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "3085683cb2c0aaf15e7449e75a1b2468ae4c570b003165073fa5368b40cb291d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Modal",
|
||||
"type": "snippetListing",
|
||||
"title": "Modal",
|
||||
"attributes": {
|
||||
"text": "Renders a Modal component, controllable through events.\nTo use the component, import `Modal` only once and then display it by passing a boolean value to the `isVisible` attribute.\n\n* Use object destructuring to set defaults for certain attributes of the modal component.\n* Define `keydownHandler`, a method which handles all keyboard events, which can be used according to your needs to dispatch actions (e.g. close the modal when <kbd>Esc</kbd> is pressed).\n* Use `React.useEffect()` hook to add or remove the `keydown` event listener, which calls `keydownHandler`.\n* Use the `isVisible` prop to determine if the modal should be shown or not.\n* Use CSS to style and position the modal component.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"effect",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "0d5247010e291b94e755d31e2cf173c232f6e37fcf1de51e39de5c6bc20c11b9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "MultiselectCheckbox",
|
||||
"type": "snippetListing",
|
||||
"title": "MultiselectCheckbox",
|
||||
"attributes": {
|
||||
"text": "Renders a checkbox list that uses a callback function to pass its selected value/values to the parent component.\n\n* Use `React.setState()` to create a `data` state variable and set its initial value equal to the `options` prop.\n* Create a function `toggle` that is used to toggle the `checked` to update the `data` state variable and call the `onChange` callback passed via the component's props.\n* Render a `<ul>` element and use `Array.prototype.map()` to map the `data` state variable to individual `<li>` elements with `<input>` elements as their children.\n* Each `<input>` element has the `type='checkbox'` attribute and is marked as `readOnly`, as its click events are handled by the parent `<li>` element's `onClick` handler.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"array",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "d99b24f434ac1dc8e9a3e424fc508f333ba95eabf4629f81eb581e8604645460"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "PasswordRevealer",
|
||||
"type": "snippetListing",
|
||||
"title": "PasswordRevealer",
|
||||
"attributes": {
|
||||
"text": "Renders a password input field with a reveal button.\n\n* Use the `React.useState()` hook to create the `shown` state variable and set its value to `false`.\n* Use a`<div>` to wrap both the`<input>` and the `<button>` element that toggles the type of the input field between `\"text\"` and `\"password\"`.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "5b4adfdb553deb652a7a16eb6d7a42a4ae9e7f1372dc28dfedfe0b54a0cca6cd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Select",
|
||||
"type": "snippetListing",
|
||||
"title": "Select",
|
||||
"attributes": {
|
||||
"text": "Renders a `<select>` 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 `<select>` element.\n* Render a `<select>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the textarea to the parent.\n* Use destructuring on the `values` array to pass an array of `value` and `text` elements and the `selected` attribute to define the initial `value` of the `<select>` element.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "078ac3ecaf21e2b8321d038eec31d04164cbc2307bacc6ad852a591cd89acc8d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Slider",
|
||||
"type": "snippetListing",
|
||||
"title": "Slider",
|
||||
"attributes": {
|
||||
"text": "Renders a slider 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 `<input>` element.\n* Render an `<input>` element of type `\"range\"` and the appropriate attributes, use the `callback` function in the `onChange` event to pass the value of the input to the parent.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "ee5e5225156f65a573b0f4c57ae4e81489bd45071bc5ff435a93c79926e3e360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "StarRating",
|
||||
"type": "snippetListing",
|
||||
"title": "StarRating",
|
||||
"attributes": {
|
||||
"text": "Renders a star rating component.\n\n* Define a component, called `Star` that will render each individual star with the appropriate appearance, based on the parent component's state.\n* In the `StarRating` component, use the `React.useState()` hook to define the `rating` and `selection` state variables with the initial values of `props.rating` (or `0` if invalid or not supplied) and `0`.\n* Create a method, `hoverOver`, that updates `selected` and `rating` according to the provided `event`.\n* Create a `<div>` to wrap the `<Star>` components, which are created using `Array.prototype.map` on an array of 5 elements, created using `Array.from`, and handle the `onMouseLeave` event to set `selection` to `0`, the `onClick` event to set the `rating` and the `onMouseOver` event to set `selection` to the `star-id` attribute of the `event.target` respectively.\n* Finally, pass the appropriate values to each `<Star>` component (`starId` and `marked`).\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"input",
|
||||
"state",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "1d0839c60acd1128737445aefbf3b98c03ba3ca1cd5203bdb585400811e5fa1f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Tabs",
|
||||
"type": "snippetListing",
|
||||
"title": "Tabs",
|
||||
"attributes": {
|
||||
"text": "Renders a tabbed menu and view component.\n\n* Define a `TabItem` component, pass it to the `Tab` and remove unnecessary nodes expect for `TabItem` by identifying the function's name in `props.children`.\n* Use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.\n* Use `Array.prototype.map` on the collected nodes to render the `tab-menu` and `tab-view`.\n* Define `changeTab`, which will be executed when clicking a `<button>` from the `tab-menu`.\n* `changeTab` executes the passed callback, `onTabClick` and updates `bindIndex`, which in turn causes a re-render, evaluating the `style` and `className` of the `tab-view` items and `tab-menu` buttons according to their `index`.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"children",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "a342c4f735b7220c902079c6af051df255200e7376a0cd6bd0a58daa7c24a243"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "TextArea",
|
||||
"type": "snippetListing",
|
||||
"title": "TextArea",
|
||||
"attributes": {
|
||||
"text": "Renders a `<textarea>` 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 `<textarea>` element.\n* Render a `<textarea>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the textarea to the parent.\n\n",
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "edfcd4ef343323d358233bf14fe3299ef55934c5bcf774246522f82df1776a31"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Ticker",
|
||||
"type": "snippetListing",
|
||||
"title": "Ticker",
|
||||
"attributes": {
|
||||
"text": "Renders a ticker component.\n\n* Use the `React.useState()` hook to initialize the `ticker` state variable to `0`.\n* Define two methods, `tick` and `reset`, that will periodically increment `timer` based on `interval` and reset `interval` respectively.\n* Return a `<div>` with two `<button>` elements, each of which calls `tick` and `reset` respectively.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "23266b3e0b4e2ab6d18bdafe0f4558c069e51f7f6480e3b51d46358fe90d4809"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Toggle",
|
||||
"type": "snippetListing",
|
||||
"title": "Toggle",
|
||||
"attributes": {
|
||||
"text": "Renders a toggle component.\n\n* Use the `React.useState()` to initialize the `isToggleOn` state variable to `false`.\n* Use an object, `style`, to hold the styles for individual components and their states.\n* Return a `<button>` that alters the component's `isToggledOn` when its `onClick` event is fired and determine the appearance of the content based on `isToggleOn`, applying the appropriate CSS rules from the `style` object.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "a497d9a55df0c12999f08d0cd9c169b5d5c8e4f56d80ab7550ef539dc73f4fcf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Tooltip",
|
||||
"type": "snippetListing",
|
||||
"title": "Tooltip",
|
||||
"attributes": {
|
||||
"text": "Renders a tooltip component.\n\n* Use the `React.useState()` hook to create the `show` variable and initialize it to `false`.\n* Return a `<div>` element that contains the `<div>` that will be the tooltip and the `children` passed to the component.\n* Handle the `onMouseEnter` and `onMouseLeave` methods, by altering the value of the `show` variable.\n\n",
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"children",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "fc1cec890de96741cba636008aa04af5f63cb127f4fedec61e31394c02fd4dbf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "TreeView",
|
||||
"type": "snippetListing",
|
||||
"title": "TreeView",
|
||||
"attributes": {
|
||||
"text": "Renders a tree view of a JSON object or array with collapsible content.\n\n* Use object destructuring to set defaults for certain props.\n* Use the value of the `toggled` prop to determine the initial state of the content (collapsed/expanded).\n* Use the `React.setState()` hook to create the `isToggled` state variable and give it the value of the `toggled` prop initially.\n* Return a `<div>` to wrap the contents of the component and the `<span>` element, used to alter the component's `isToggled` state.\n* Determine the appearance of the component, based on `isParentToggled`, `isToggled`, `name` and `Array.isArray()` on `data`.\n* For each child in `data`, determine if it is an object or array and recursively render a sub-tree.\n* Otherwise, render a `<p>` element with the appropriate style.\n\n",
|
||||
"tags": [
|
||||
"object",
|
||||
"visual",
|
||||
"state",
|
||||
"recursion",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "4002acde51d7ecf13830a102da4ca83b702b50c9603834d8902dfba859d955e4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "UncontrolledInput",
|
||||
"type": "snippetListing",
|
||||
"title": "UncontrolledInput",
|
||||
"attributes": {
|
||||
"text": "Renders an `<input>` 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 `<input>` element.\n* Render an `<input>` 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",
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "7ea3169ca176a8bf0610f34082363a18811d16451519b62776ffae9f0175820c"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"specification": "http://jsonapi.org/format/",
|
||||
"type": "snippetListingArray"
|
||||
}
|
||||
}
|
||||
593
snippet_data/snippets.json
Normal file
593
snippet_data/snippets.json
Normal file
@ -0,0 +1,593 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "Accordion",
|
||||
"title": "Accordion",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Accordion.md",
|
||||
"text": "Renders an accordion menu with multiple collapsible content components.\n\n* Define an `AccordionItem` component, pass it to the `Accordion` and remove unnecessary nodes expect for `AccordionItem` by identifying the function's name in `props.children`.\n* Each `AccordionItem` component renders a `<button>` that is used to update the `Accordion` via the `props.handleClick` callback and the content of the component, passed down via `props.children`, while its appearance is determined by `props.isCollapsed` and based on `style`.\n* In the `Accordion` component, use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.\n* Use `Array.prototype.map` on the collected nodes to render the individual collapsiple elements.\n* Define `changeItem`, which will be executed when clicking an `AccordionItem`'s `<button>`.\n`changeItem` executes the passed callback, `onItemClick` and updates `bindIndex` based on the clicked element.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function AccordionItem(props) {\r\n const style = {\r\n collapsed: {\r\n display: 'none'\r\n },\r\n expanded: {\r\n display: 'block'\r\n },\r\n buttonStyle: {\r\n display: 'block',\r\n width: '100%'\r\n }\r\n };\r\n\r\n return (\r\n <div>\r\n <button style={style.buttonStyle} onClick={() => props.handleClick()}>\r\n {props.label}\r\n </button>\r\n <div\r\n className=\"collapse-content\"\r\n style={props.isCollapsed ? style.collapsed : style.expanded}\r\n aria-expanded={props.isCollapsed}\r\n >\r\n {props.children}\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nfunction Accordion(props) {\r\n const [bindIndex, setBindIndex] = React.useState(props.defaultIndex);\r\n\r\n const changeItem = itemIndex => {\r\n if (typeof props.onItemClick === 'function') props.onItemClick(itemIndex);\r\n if (itemIndex !== bindIndex) setBindIndex(itemIndex);\r\n };\r\n const items = props.children.filter(item => item.type.name === 'AccordionItem');\r\n\r\n return (\r\n <div className=\"wrapper\">\r\n {items.map(({ props }) => (\r\n <AccordionItem\r\n isCollapsed={bindIndex === props.index}\r\n label={props.label}\r\n handleClick={() => changeItem(props.index)}\r\n children={props.children}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <Accordion defaultIndex=\"1\" onItemClick={console.log}>\r\n <AccordionItem label=\"A\" index=\"1\">\r\n Lorem ipsum\r\n </AccordionItem>\r\n <AccordionItem label=\"B\" index=\"2\">\r\n Dolor sit amet\r\n </AccordionItem>\r\n </Accordion>,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "7787feea6f6ad8bb1b1a73cf2938673d41fbc0dc8b29d5ebbbafe572784d963e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "AutoLink",
|
||||
"title": "AutoLink",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "AutoLink.md",
|
||||
"text": "Renders a string as plaintext, with URLs converted to appropriate `<a>` elements.\n\n* Use `String.prototype.split()` and `String.prototype.match()` with a regular expression to find URLs in a string.\n* Return a `<React.Fragment>` with matched URLs rendered as `<a>` elements, dealing with missing protocol prefixes if necessary, and the rest of the string rendered as plaintext.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function AutoLink({ text }) {\r\n const delimiter = /((?:https?:\\/\\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\\-]{1,61}[a-z0-9])?\\.[^\\.|\\s])+[a-z\\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\\d{1,5})*[a-z0-9.,_\\/~#&=;%+?\\-\\\\(\\\\)]*)/gi;\r\n\r\n return (\r\n <React.Fragment>\r\n {text.split(delimiter).map(word => {\r\n let match = word.match(delimiter);\r\n if (match) {\r\n let url = match[0];\r\n return <a href={url.startsWith('http') ? url : `http://${url}`}>{url}</a>;\r\n }\r\n return word;\r\n })}\r\n </React.Fragment>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <AutoLink text=\"foo bar baz http://example.org bar\" />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"string",
|
||||
"fragment",
|
||||
"regexp",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "4ca8f86fe197110cbdc91241f1c93ccb31ebe23f9b8eabe00f23c31f1939db1f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Carousel",
|
||||
"title": "Carousel",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Carousel.md",
|
||||
"text": "Renders a carousel component.\n\n* Use the `React.setState()` hook to create the `active` state variable and give it a value of `0` (index of the first item).\n* Use an object, `style`, to hold the styles for the individual components.\n* Use the `React.setEffect()` hook to update the value of `active` to the index of the next item, using `setTimeout`.\n* Destructure `props`, compute if visibility style should be set to `visible` or not for each carousel item while mapping over and applying the combined style to the carousel item component accordingly.\n* Render the carousel items using `React.cloneElement()` and pass down rest `props` along with the computed styles.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Carousel(props) {\r\n const [active, setActive] = React.useState(0);\r\n let scrollInterval = null;\r\n const style = {\r\n carousel: {\r\n position: 'relative'\r\n },\r\n carouselItem: {\r\n position: 'absolute',\r\n visibility: 'hidden'\r\n },\r\n visible: {\r\n visibility: 'visible'\r\n }\r\n };\r\n React.useEffect(() => {\r\n scrollInterval = setTimeout(() => {\r\n const { carouselItems } = props;\r\n setActive((active + 1) % carouselItems.length);\r\n }, 2000);\r\n });\r\n const { carouselItems, ...rest } = props;\r\n return (\r\n <div style={style.carousel}>\r\n {carouselItems.map((item, index) => {\r\n const activeStyle = active === index ? style.visible : {};\r\n return React.cloneElement(item, {\r\n ...rest,\r\n style: {\r\n ...style.carouselItem,\r\n ...activeStyle\r\n }\r\n });\r\n })}\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <Carousel\r\n carouselItems={[\r\n <div>carousel item 1</div>,\r\n <div>carousel item 2</div>,\r\n <div>carousel item 3</div>\r\n ]}\r\n />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state",
|
||||
"effect",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "c585316c31ab75c906dd8a9023474bfd68eb37dde735039bcca6067a2acd2894"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ClickOutAndInside",
|
||||
"title": "ClickInside and ClickOutside",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "ClickOutAndInside.md",
|
||||
"text": "Two handy hooks to handle the click outside and inside event on the wrapped component.\n\n* Create customized hooks that take in a `ref` component(node) and a `callback` function to hanlde the customized `click` event\n* Use the `React.useEffect()` hook to append and clean up the `click` event.\n* Use the `React.useRef()` hook to create a `ref` for your click component and pass it to `useClickInside` and `useClickOutside` hooks.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": ".click-box {\r\n border: 2px dashed orangered;\r\n height: 200px;\r\n width: 400px;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}\r\n\r\np {\r\n border: 2px solid blue;\r\n padding: 16px;\r\n}",
|
||||
"code": "const useClickInside = (ref, callback) => {\r\n const handleClick = e => {\r\n //use the node contains to verify if we click inside\r\n if (ref.current && ref.current.contains(e.target)) {\r\n callback();\r\n }\r\n };\r\n\r\n //clean up using useEffect\r\n useEffect(() => {\r\n document.addEventListener(\"click\", handleClick);\r\n return () => {\r\n document.removeEventListener(\"click\", handleClick);\r\n };\r\n });\r\n};\r\n\r\nconst useClickOutside = (ref, callback) => {\r\n const handleClick = e => {\r\n //use the node contains to verify if we click outside\r\n if (ref.current && !ref.current.contains(e.target)) {\r\n callback();\r\n }\r\n };\r\n // clean up using useEffect\r\n useEffect(() => {\r\n document.addEventListener(\"click\", handleClick);\r\n return () => {\r\n document.removeEventListener(\"click\", handleClick);\r\n };\r\n });\r\n};\r\n\r\nfunction ClickBox({onClickOutside,onClickInside}) {\r\n const clickRef = useRef();\r\n useClickOutside(clickRef, onClickOutside);\r\n useClickInside(clickRef, onClickInside);\r\n return (\r\n <div className=\"click-box\" ref={clickRef}>\r\n <p>Hello Click Me Inside!</p>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<ClickBox onClickOutside={()=> alert(\"click outside\")} onClickInside={()=> alert(\"click inside\")}/>,document.getElementById('root'))"
|
||||
},
|
||||
"tags": [
|
||||
"hooks",
|
||||
"effect",
|
||||
"event",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "773cb2ce0e28ff705f350ff82398836268bce65b32a2b93664bd535fbeed61e8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Collapse",
|
||||
"title": "Collapse",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Collapse.md",
|
||||
"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 `<div>` to wrap both the `<button>` 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": {
|
||||
"style": "",
|
||||
"code": "function Collapse(props) {\r\n const [isCollapsed, setIsCollapsed] = React.useState(props.collapsed);\r\n\r\n const style = {\r\n collapsed: {\r\n display: 'none'\r\n },\r\n expanded: {\r\n display: 'block'\r\n },\r\n buttonStyle: {\r\n display: 'block',\r\n width: '100%'\r\n }\r\n };\r\n\r\n return (\r\n <div>\r\n <button style={style.buttonStyle} onClick={() => setIsCollapsed(!isCollapsed)}>\r\n {isCollapsed ? 'Show' : 'Hide'} content\r\n </button>\r\n <div\r\n className=\"collapse-content\"\r\n style={isCollapsed ? style.collapsed : style.expanded}\r\n aria-expanded={isCollapsed}\r\n >\r\n {props.children}\r\n </div>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <Collapse>\r\n <h1>This is a collapse</h1>\r\n <p>Hello world!</p>\r\n </Collapse>,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"state",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "0fb184a53478714a3eb9d9906f7661d5fa416db6bdb2b2b819add2ffb38f2a22"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "CountDown",
|
||||
"title": "CountDown",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "CountDown.md",
|
||||
"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 `<div>` to wrap a `<p>` element with the textual representation of the components `time` state variable, as well as two `<button>` 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": {
|
||||
"style": "",
|
||||
"code": "function CountDown({ hours = 0, minutes = 0, seconds = 0 }) {\r\n const [paused, setPaused] = React.useState(false);\r\n const [over, setOver] = React.useState(false);\r\n const [time, setTime] = React.useState({\r\n hours: parseInt(hours),\r\n minutes: parseInt(minutes),\r\n seconds: parseInt(seconds)\r\n });\r\n\r\n const tick = () => {\r\n if (paused || over) return;\r\n if (time.hours == 0 && time.minutes == 0 && time.seconds == 0) setOver(true);\r\n else if (time.minutes == 0 && time.seconds == 0)\r\n setTime({\r\n hours: time.hours - 1,\r\n minutes: 59,\r\n seconds: 59\r\n });\r\n else if (time.seconds == 0)\r\n setTime({\r\n hours: time.hours,\r\n minutes: time.minutes - 1,\r\n seconds: 59\r\n });\r\n else\r\n setTime({\r\n hours: time.hours,\r\n minutes: time.minutes,\r\n seconds: time.seconds - 1\r\n });\r\n };\r\n\r\n const reset = () => {\r\n setTime({\r\n hours: parseInt(hours),\r\n minutes: parseInt(minutes),\r\n seconds: parseInt(seconds)\r\n });\r\n setPaused(false);\r\n setOver(false);\r\n };\r\n\r\n React.useEffect(() => {\r\n let timerID = setInterval(() => tick(), 1000);\r\n return () => clearInterval(timerID);\r\n });\r\n\r\n return (\r\n <div>\r\n <p>{`${time.hours.toString().padStart(2, '0')}:${time.minutes\r\n .toString()\r\n .padStart(2, '0')}:${time.seconds.toString().padStart(2, '0')}`}</p>\r\n <div>{over ? \"Time's up!\" : ''}</div>\r\n <button onClick={() => setPaused(!paused)}>{paused ? 'Resume' : 'Pause'}</button>\r\n <button onClick={() => reset()}>Restart</button>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<CountDown hours=\"1\" minutes=\"45\" />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "cab16a22ce97e7a90a4ca0717b62a185bcbcd37fa55dc6983c79ea5857f76aaf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "DataList",
|
||||
"title": "DataList",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "DataList.md",
|
||||
"text": "Renders a list of elements from an array of primitives.\n\n* Use the value of the `isOrdered` prop to conditionally render a `<ol>` or `<ul>` list.\n* Use `Array.prototype.map` to render every item in `data` as a `<li>` element, give it a `key` produced from the concatenation of the its index and value.\n* Omit the `isOrdered` prop to render a `<ul>` list by default.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function DataList({ isOrdered, data }) {\r\n const list = data.map((val, i) => <li key={`${i}_${val}`}>{val}</li>);\r\n return isOrdered ? <ol>{list}</ol> : <ul>{list}</ul>;\r\n}",
|
||||
"example": "const names = ['John', 'Paul', 'Mary'];\r\nReactDOM.render(<DataList data={names} />, document.getElementById('root'));\r\nReactDOM.render(<DataList data={names} isOrdered />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"array",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "0280f7d4991a3145a1263342ef5ffa2e845c24c0793a2a06d40ed29a450b1039"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "DataTable",
|
||||
"title": "DataTable",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "DataTable.md",
|
||||
"text": "Renders a table with rows dynamically created from an array of primitives.\n\n* Render a `<table>` element with two columns (`ID` and `Value`).\n* Use `Array.prototype.map` to render every item in `data` as a `<tr>` element, consisting of its index and value, give it a `key` produced from the concatenation of the two.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function DataTable({ data }) {\r\n return (\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>ID</th>\r\n <th>Value</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {data.map((val, i) => (\r\n <tr key={`${i}_${val}`}>\r\n <td>{i}</td>\r\n <td>{val}</td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n );\r\n}",
|
||||
"example": "const people = ['John', 'Jesse'];\r\nReactDOM.render(<DataTable data={people} />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"array",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "6da7e808336fe96d699685abb7d93735693f6e8dfba4a84262b9e02a624da617"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "FileDrop",
|
||||
"title": "FileDrop",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "FileDrop.md",
|
||||
"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 `<div>` and use `drag` and `filename` to determine its contents and style.\n* Finally, bind the `ref` of the created `<div>` to `dropRef`.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": ".filedrop {\r\n min-height: 120px;\r\n border: 3px solid #d3d3d3;\r\n text-align: center;\r\n font-size: 24px;\r\n padding: 32px;\r\n border-radius: 4px;\r\n}\r\n\r\n.filedrop.drag {\r\n border: 3px dashed #1e90ff;\r\n}\r\n\r\n.filedrop.ready {\r\n border: 3px solid #32cd32;\r\n}",
|
||||
"code": "function FileDrop(props) {\r\n const [drag, setDrag] = React.useState(false);\r\n const [filename, setFilename] = React.useState('');\r\n let dropRef = React.createRef();\r\n let dragCounter = 0;\r\n\r\n const handleDrag = e => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n };\r\n\r\n const handleDragIn = e => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n dragCounter++;\r\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) setDrag(true);\r\n };\r\n\r\n const handleDragOut = e => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n dragCounter--;\r\n if (dragCounter === 0) setDrag(false);\r\n };\r\n\r\n const handleDrop = e => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n setDrag(false);\r\n if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\r\n props.handleDrop(e.dataTransfer.files[0]);\r\n setFilename(e.dataTransfer.files[0].name);\r\n e.dataTransfer.clearData();\r\n dragCounter = 0;\r\n }\r\n };\r\n\r\n React.useEffect(() => {\r\n let div = dropRef.current;\r\n div.addEventListener('dragenter', handleDragIn);\r\n div.addEventListener('dragleave', handleDragOut);\r\n div.addEventListener('dragover', handleDrag);\r\n div.addEventListener('drop', handleDrop);\r\n return function cleanup() {\r\n div.removeEventListener('dragenter', handleDragIn);\r\n div.removeEventListener('dragleave', handleDragOut);\r\n div.removeEventListener('dragover', handleDrag);\r\n div.removeEventListener('drop', handleDrop);\r\n };\r\n });\r\n\r\n return (\r\n <div\r\n ref={dropRef}\r\n className={drag ? 'filedrop drag' : filename ? 'filedrop ready' : 'filedrop'}\r\n >\r\n {filename && !drag ? <div>{filename}</div> : <div>Drop files here!</div>}\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<FileDrop handleDrop={console.log} />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"input",
|
||||
"state",
|
||||
"effect",
|
||||
"event",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "79742466c371960a0134df6158931d6468a2767fc0d21e20b6709bb46c737941"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "LimitedTextarea",
|
||||
"title": "LimitedTextarea",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "LimitedTextarea.md",
|
||||
"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`<div>` to wrap both the`<textarea>` and the `<p>` element that displays the character count and bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function LimitedTextarea({ rows, cols, value, limit }) {\r\n const [content, setContent] = React.useState(value);\r\n\r\n const setFormattedContent = text => {\r\n text.length > limit ? setContent(text.slice(0, limit)) : setContent(text);\r\n };\r\n\r\n React.useEffect(() => {\r\n setFormattedContent(content);\r\n }, []);\r\n\r\n return (\r\n <div>\r\n <textarea\r\n rows={rows}\r\n cols={cols}\r\n onChange={event => setFormattedContent(event.target.value)}\r\n value={content}\r\n />\r\n <p>\r\n {content.length}/{limit}\r\n </p>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<LimitedTextarea limit={32} value=\"Hello!\" />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect",
|
||||
"event",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "a9aa0a8f818ba02a861fca0f91a9a164dedcf7683b5d38391003e1864a79e627"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "LimitedWordTextarea",
|
||||
"title": "LimitedWordTextarea",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "LimitedWordTextarea.md",
|
||||
"text": "Renders a textarea component with a word limit.\n\n* Use the `React.useState()` hook to create the `content` and `wordCount` state variables and set their values to `value` and `0` respectively.\n* Create a method `setFormattedContent`, which uses `String.prototype.split(' ')` to turn the input into an array of words and check if the result of applying `Array.prototype.filter(Boolean)` has a `length` longer than `limit`.\n* If the afforementioned `length` exceeds the `limit`, trim the input, otherwise return the raw input, updating `content` and `wordCount` accordingly in both cases.\n* Use the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.\n* Use a`<div>` to wrap both the`<textarea>` and the `<p>` element that displays the character count and bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function LimitedWordTextarea({ rows, cols, value, limit }) {\r\n const [content, setContent] = React.useState(value);\r\n const [wordCount, setWordCount] = React.useState(0);\r\n\r\n const setFormattedContent = text => {\r\n let words = text.split(' ');\r\n if (words.filter(Boolean).length > limit) {\r\n setContent(\r\n text\r\n .split(' ')\r\n .slice(0, limit)\r\n .join(' ')\r\n );\r\n setWordCount(limit);\r\n } else {\r\n setContent(text);\r\n setWordCount(words.filter(Boolean).length);\r\n }\r\n };\r\n\r\n React.useEffect(() => {\r\n setFormattedContent(content);\r\n }, []);\r\n\r\n return (\r\n <div>\r\n <textarea\r\n rows={rows}\r\n cols={cols}\r\n onChange={event => setFormattedContent(event.target.value)}\r\n value={content}\r\n />\r\n <p>\r\n {wordCount}/{limit}\r\n </p>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <LimitedWordTextArea limit={5} value=\"Hello there!\" />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect",
|
||||
"event",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "88c10a294c994103d88ab6793cbf290b707d93f6cd0cf9272186e148c0a739bf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Mailto",
|
||||
"title": "Mailto",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Mailto.md",
|
||||
"text": "Renders a link formatted to send an email.\n\n* Destructure the component's props, use `email`, `subject` and `body` to create a `<a>` element with an appropriate `href` attribute.\n* Render the link with `props.children` as its content.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Mailto({ email, subject, body, ...props }) {\r\n return (\r\n <a href={`mailto:${email}?subject=${subject || ''}&body=${body || ''}`}>{props.children}</a>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <Mailto email=\"foo@bar.baz\" subject=\"Hello\" body=\"Hello world!\">\r\n Mail me!\r\n </Mailto>,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"viual",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "1af32eb31db03931a3d0ec99311b0404f4f1b34a597b6fdf8a000b5d3348e09f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "MappedTable",
|
||||
"title": "MappedTable",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "MappedTable.md",
|
||||
"text": "Renders a table with rows dynamically created from an array of objects and a list of property names.\n\n* Use `Object.keys()`, `Array.prototype.filter()`, `Array.prototype.includes()` and `Array.prototype.reduce()` to produce a `filteredData` array, containing all objects with the keys specified in `propertyNames`.\n* Render a `<table>` element with a set of columns equal to the amount of values in `propertyNames`.\n* Use `Array.prototype.map` to render each value in the `propertyNames` array as a `<th>` element.\n* Use `Array.prototype.map` to render each object in the `filteredData` array as a `<tr>` element, containing a `<td>` for each key in the object.\n\n*This component does not work with nested objects and will break if there are nested objects inside any of the properties specified in `propertyNames`*\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function MappedTable({ data, propertyNames }) {\r\n let filteredData = data.map(v =>\r\n Object.keys(v)\r\n .filter(k => propertyNames.includes(k))\r\n .reduce((acc, key) => ((acc[key] = v[key]), acc), {})\r\n );\r\n return (\r\n <table>\r\n <thead>\r\n <tr>\r\n {propertyNames.map(val => (\r\n <th key={`h_${val}`}>{val}</th>\r\n ))}\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {filteredData.map((val, i) => (\r\n <tr key={`i_${i}`}>\r\n {propertyNames.map(p => (\r\n <td key={`i_${i}_${p}`}>{val[p]}</td>\r\n ))}\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n );\r\n}",
|
||||
"example": "const people = [\r\n { name: 'John', surname: 'Smith', age: 42 },\r\n { name: 'Adam', surname: 'Smith', gender: 'male' }\r\n];\r\nconst propertyNames = ['name', 'surname', 'age'];\r\nReactDOM.render(\r\n <MappedTable data={people} propertyNames={propertyNames} />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"array",
|
||||
"object",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "3085683cb2c0aaf15e7449e75a1b2468ae4c570b003165073fa5368b40cb291d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Modal",
|
||||
"title": "Modal",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Modal.md",
|
||||
"text": "Renders a Modal component, controllable through events.\nTo use the component, import `Modal` only once and then display it by passing a boolean value to the `isVisible` attribute.\n\n* Use object destructuring to set defaults for certain attributes of the modal component.\n* Define `keydownHandler`, a method which handles all keyboard events, which can be used according to your needs to dispatch actions (e.g. close the modal when <kbd>Esc</kbd> is pressed).\n* Use `React.useEffect()` hook to add or remove the `keydown` event listener, which calls `keydownHandler`.\n* Use the `isVisible` prop to determine if the modal should be shown or not.\n* Use CSS to style and position the modal component.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": ".modal {\r\n position: fixed;\r\n top: 0;\r\n bottom: 0;\r\n left: 0;\r\n right:0;\r\n width: 100%;\r\n z-index: 9999; \r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background-color: rgba(0, 0, 0, 0.25);\r\n animation-name: appear;\r\n animation-duration: 300ms;\r\n}\r\n\r\n.modal-dialog{\r\n width: 100%;\r\n max-width: 550px;\r\n background: white;\r\n position: relative;\r\n margin: 0 20px;\r\n max-height: calc(100vh - 40px);\r\n text-align: left;\r\n display: flex;\r\n flex-direction: column;\r\n overflow:hidden;\r\n box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);\r\n -webkit-animation-name: animatetop;\r\n -webkit-animation-duration: 0.4s;\r\n animation-name: slide-in;\r\n animation-duration: 0.5s;\r\n}\r\n\r\n.modal-header,.modal-footer{\r\n display: flex;\r\n align-items: center;\r\n padding: 1rem;\r\n}\r\n.modal-header{\r\n border-bottom: 1px solid #dbdbdb;\r\n justify-content: space-between;\r\n}\r\n.modal-footer{\r\n border-top: 1px solid #dbdbdb;\r\n justify-content: flex-end;\r\n}\r\n.modal-close{\r\n cursor: pointer;\r\n padding: 1rem;\r\n margin: -1rem -1rem -1rem auto;\r\n}\r\n.modal-body{\r\n overflow: auto;\r\n}\r\n.modal-content{\r\n padding: 1rem;\r\n}\r\n\r\n@keyframes appear {\r\n from {opacity: 0;}\r\n to {opacity: 1;}\r\n}\r\n@keyframes slide-in {\r\n from {transform: translateY(-150px);}\r\n to { transform: translateY(0);}\r\n}",
|
||||
"code": "function Modal({ isVisible = false, title, content, footer, onClose }){ \r\n React.useEffect(() => {\r\n document.addEventListener('keydown', keydownHandler);\r\n return () => document.removeEventListener('keydown', keydownHandler);\r\n });\r\n\r\n function keydownHandler({ key }) {\r\n switch (key) {\r\n case 'Escape': onClose(); break;\r\n default:\r\n }\r\n }\r\n\r\n return !isVisible ? null : (\r\n <div className=\"modal\" onClick={onClose}>\r\n <div className=\"modal-dialog\" onClick={e => e.stopPropagation()}>\r\n <div className=\"modal-header\">\r\n <h3 className=\"modal-title\">{title}</h3>\r\n <span className=\"modal-close\" onClick={onClose}>×</span>\r\n </div>\r\n <div className=\"modal-body\">\r\n <div className=\"modal-content\">{ content }</div>\r\n </div>\r\n {footer && <div className=\"modal-footer\">{footer}</div>}\r\n </div>\r\n </div>\r\n )\r\n}",
|
||||
"example": "//Add the component to the render function\r\nfunction App() {\r\n const [ isModal, setModal] = React.useState(false);\r\n \r\n return (\r\n <React.Fragment>\r\n <button onClick={()=> setModal(true)}>Click Here</button>\r\n <Modal \r\n isVisible={ isModal }\r\n title= \"Modal Title\"\r\n content = {<p>Add your content here</p>}\r\n footer = {<button>Cancel</button>}\r\n onClose ={()=> setModal(false)}\r\n />\r\n </React.Fragment>\r\n )\r\n}\r\n\r\nReactDOM.render( <App/>, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"effect",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "0d5247010e291b94e755d31e2cf173c232f6e37fcf1de51e39de5c6bc20c11b9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "MultiselectCheckbox",
|
||||
"title": "MultiselectCheckbox",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "MultiselectCheckbox.md",
|
||||
"text": "Renders a checkbox list that uses a callback function to pass its selected value/values to the parent component.\n\n* Use `React.setState()` to create a `data` state variable and set its initial value equal to the `options` prop.\n* Create a function `toggle` that is used to toggle the `checked` to update the `data` state variable and call the `onChange` callback passed via the component's props.\n* Render a `<ul>` element and use `Array.prototype.map()` to map the `data` state variable to individual `<li>` elements with `<input>` elements as their children.\n* Each `<input>` element has the `type='checkbox'` attribute and is marked as `readOnly`, as its click events are handled by the parent `<li>` element's `onClick` handler.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "const style = {\r\n listContainer: {\r\n listStyle: 'none',\r\n paddingLeft: 0\r\n },\r\n itemStyle: {\r\n cursor: 'pointer',\r\n padding: 5\r\n }\r\n};\r\n\r\nfunction MultiselectCheckbox({ options, onChange }) {\r\n const [data, setData] = React.useState(options);\r\n\r\n const toggle = item => {\r\n data.forEach((_, key) => {\r\n if (data[key].label === item.label) data[key].checked = !item.checked;\r\n });\r\n setData([...data]);\r\n onChange(data);\r\n };\r\n\r\n return (\r\n <ul style={style.listContainer}>\r\n {data.map(item => {\r\n return (\r\n <li key={item.label} style={style.itemStyle} onClick={() => toggle(item)}>\r\n <input readOnly type=\"checkbox\" checked={item.checked || false} />\r\n {item.label}\r\n </li>\r\n );\r\n })}\r\n </ul>\r\n );\r\n}",
|
||||
"example": "const options = [{ label: 'Item One' }, { label: 'Item Two' }];\r\n\r\nReactDOM.render(\r\n <MultiselectCheckbox\r\n options={options}\r\n onChange={data => {\r\n console.log(data);\r\n }}\r\n />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"array",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "d99b24f434ac1dc8e9a3e424fc508f333ba95eabf4629f81eb581e8604645460"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "PasswordRevealer",
|
||||
"title": "PasswordRevealer",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "PasswordRevealer.md",
|
||||
"text": "Renders a password input field with a reveal button.\n\n* Use the `React.useState()` hook to create the `shown` state variable and set its value to `false`.\n* Use a`<div>` to wrap both the`<input>` and the `<button>` element that toggles the type of the input field between `\"text\"` and `\"password\"`.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function PasswordRevealer({ value }) {\r\n const [shown, setShown] = React.useState(false);\r\n\r\n return (\r\n <div>\r\n <input type={shown ? 'text' : 'password'} value={value} onChange={() => {}} />\r\n <button onClick={() => setShown(!shown)}>Show/Hide</button>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<PasswordRevealer />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "5b4adfdb553deb652a7a16eb6d7a42a4ae9e7f1372dc28dfedfe0b54a0cca6cd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Select",
|
||||
"title": "Select",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Select.md",
|
||||
"text": "Renders a `<select>` 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 `<select>` element.\n* Render a `<select>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the textarea to the parent.\n* Use destructuring on the `values` array to pass an array of `value` and `text` elements and the `selected` attribute to define the initial `value` of the `<select>` element.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Select({ values, callback, disabled = false, readonly = false, selected }) {\r\n return (\r\n <select\r\n disabled={disabled}\r\n readOnly={readonly}\r\n onChange={({ target: { value } }) => callback(value)}\r\n >\r\n {values.map(([value, text]) => (\r\n <option selected={selected === value} value={value}>\r\n {text}\r\n </option>\r\n ))}\r\n </select>\r\n );\r\n}",
|
||||
"example": "let choices = [\r\n ['grapefruit', 'Grapefruit'],\r\n ['lime', 'Lime'],\r\n ['coconut', 'Coconut'],\r\n ['mango', 'Mango']\r\n];\r\nReactDOM.render(\r\n <Select values={choices} selected=\"lime\" callback={val => console.log(val)} />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "078ac3ecaf21e2b8321d038eec31d04164cbc2307bacc6ad852a591cd89acc8d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Slider",
|
||||
"title": "Slider",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Slider.md",
|
||||
"text": "Renders a slider 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 `<input>` element.\n* Render an `<input>` element of type `\"range\"` and the appropriate attributes, use the `callback` function in the `onChange` event to pass the value of the input to the parent.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Slider({ callback, disabled = false, readOnly = false }) {\r\n return (\r\n <input\r\n type=\"range\"\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n onChange={({ target: { value } }) => callback(value)}\r\n />\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<Slider callback={val => console.log(val)} />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "ee5e5225156f65a573b0f4c57ae4e81489bd45071bc5ff435a93c79926e3e360"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "StarRating",
|
||||
"title": "StarRating",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "StarRating.md",
|
||||
"text": "Renders a star rating component.\n\n* Define a component, called `Star` that will render each individual star with the appropriate appearance, based on the parent component's state.\n* In the `StarRating` component, use the `React.useState()` hook to define the `rating` and `selection` state variables with the initial values of `props.rating` (or `0` if invalid or not supplied) and `0`.\n* Create a method, `hoverOver`, that updates `selected` and `rating` according to the provided `event`.\n* Create a `<div>` to wrap the `<Star>` components, which are created using `Array.prototype.map` on an array of 5 elements, created using `Array.from`, and handle the `onMouseLeave` event to set `selection` to `0`, the `onClick` event to set the `rating` and the `onMouseOver` event to set `selection` to the `star-id` attribute of the `event.target` respectively.\n* Finally, pass the appropriate values to each `<Star>` component (`starId` and `marked`).\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Star({ marked, starId }) {\r\n return (\r\n <span star-id={starId} style={{ color: '#ff9933' }} role=\"button\">\r\n {marked ? '\\u2605' : '\\u2606'}\r\n </span>\r\n );\r\n}\r\n\r\nfunction StarRating(props) {\r\n const [rating, setRating] = React.useState(typeof props.rating == 'number' ? props.rating : 0);\r\n const [selection, setSelection] = React.useState(0);\r\n const hoverOver = event => {\r\n let val = 0;\r\n if (event && event.target && event.target.getAttribute('star-id'))\r\n val = event.target.getAttribute('star-id');\r\n setSelection(val);\r\n };\r\n return (\r\n <div\r\n onMouseOut={() => hoverOver(null)}\r\n onClick={(event) => setRating(event.target.getAttribute('star-id') || rating)}\r\n onMouseOver={hoverOver}\r\n >\r\n {Array.from({ length: 5 }, (v, i) => (\r\n <Star\r\n starId={i + 1}\r\n key={`star_${i + 1} `}\r\n marked={selection ? selection >= i + 1 : rating >= i + 1}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<StarRating />, document.getElementById('root'));\r\nReactDOM.render(<StarRating rating={2} />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"children",
|
||||
"input",
|
||||
"state",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "1d0839c60acd1128737445aefbf3b98c03ba3ca1cd5203bdb585400811e5fa1f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Tabs",
|
||||
"title": "Tabs",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Tabs.md",
|
||||
"text": "Renders a tabbed menu and view component.\n\n* Define a `TabItem` component, pass it to the `Tab` and remove unnecessary nodes expect for `TabItem` by identifying the function's name in `props.children`.\n* Use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.\n* Use `Array.prototype.map` on the collected nodes to render the `tab-menu` and `tab-view`.\n* Define `changeTab`, which will be executed when clicking a `<button>` from the `tab-menu`.\n* `changeTab` executes the passed callback, `onTabClick` and updates `bindIndex`, which in turn causes a re-render, evaluating the `style` and `className` of the `tab-view` items and `tab-menu` buttons according to their `index`.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": ".tab-menu > button {\r\n cursor: pointer;\r\n padding: 8px 16px;\r\n border: 0;\r\n border-bottom: 2px solid transparent;\r\n background: none;\r\n}\r\n.tab-menu > button.focus {\r\n border-bottom: 2px solid #007bef;\r\n}\r\n.tab-menu > button:hover {\r\n border-bottom: 2px solid #007bef;\r\n}",
|
||||
"code": "function TabItem(props) {\r\n return <div {...props} />;\r\n}\r\n\r\nfunction Tabs(props) {\r\n const [bindIndex, setBindIndex] = React.useState(props.defaultIndex);\r\n const changeTab = newIndex => {\r\n if (typeof props.onTabClick === 'function') props.onTabClick(newIndex);\r\n setBindIndex(newIndex);\r\n };\r\n const items = props.children.filter(item => item.type.name === 'TabItem');\r\n\r\n return (\r\n <div className=\"wrapper\">\r\n <div className=\"tab-menu\">\r\n {items.map(({ props: { index, label } }) => (\r\n <button onClick={() => changeTab(index)} className={bindIndex === index ? 'focus' : ''}>\r\n {label}\r\n </button>\r\n ))}\r\n </div>\r\n <div className=\"tab-view\">\r\n {items.map(({ props }) => (\r\n <div\r\n {...props}\r\n className=\"tab-view_item\"\r\n key={props.index}\r\n style={{ display: bindIndex === props.index ? 'block' : 'none' }}\r\n />\r\n ))}\r\n </div>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <Tabs defaultIndex=\"1\" onTabClick={console.log}>\r\n <TabItem label=\"A\" index=\"1\">\r\n Lorem ipsum\r\n </TabItem>\r\n <TabItem label=\"B\" index=\"2\">\r\n Dolor sit amet\r\n </TabItem>\r\n </Tabs>,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"children",
|
||||
"intermediate"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "a342c4f735b7220c902079c6af051df255200e7376a0cd6bd0a58daa7c24a243"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "TextArea",
|
||||
"title": "TextArea",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "TextArea.md",
|
||||
"text": "Renders a `<textarea>` 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 `<textarea>` element.\n* Render a `<textarea>` element with the appropriate attributes and use the `callback` function in the `onChange` event to pass the value of the textarea to the parent.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function TextArea({\r\n callback,\r\n cols = 20,\r\n rows = 2,\r\n disabled = false,\r\n readOnly = false,\r\n placeholder = ''\r\n}) {\r\n return (\r\n <textarea\r\n cols={cols}\r\n rows={rows}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n placeholder={placeholder}\r\n onChange={({ target: { value } }) => callback(value)}\r\n />\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <TextArea placeholder=\"Insert some text here...\" callback={val => console.log(val)} />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "edfcd4ef343323d358233bf14fe3299ef55934c5bcf774246522f82df1776a31"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Ticker",
|
||||
"title": "Ticker",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Ticker.md",
|
||||
"text": "Renders a ticker component.\n\n* Use the `React.useState()` hook to initialize the `ticker` state variable to `0`.\n* Define two methods, `tick` and `reset`, that will periodically increment `timer` based on `interval` and reset `interval` respectively.\n* Return a `<div>` with two `<button>` elements, each of which calls `tick` and `reset` respectively.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Ticker(props) {\r\n const [ticker, setTicker] = React.useState(0);\r\n let interval = null;\r\n\r\n const tick = () => {\r\n reset();\r\n interval = setInterval(() => {\r\n if (ticker < props.times) setTicker(ticker + 1);\r\n else clearInterval(interval);\r\n }, props.interval);\r\n };\r\n\r\n const reset = () => {\r\n setTicker(0);\r\n clearInterval(interval);\r\n };\r\n\r\n return (\r\n <div>\r\n <span style={{ fontSize: 100 }}>{ticker}</span>\r\n <button onClick={tick}>Tick!</button>\r\n <button onClick={reset}>Reset</button>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<Ticker times={5} interval={1000} />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "23266b3e0b4e2ab6d18bdafe0f4558c069e51f7f6480e3b51d46358fe90d4809"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Toggle",
|
||||
"title": "Toggle",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Toggle.md",
|
||||
"text": "Renders a toggle component.\n\n* Use the `React.useState()` to initialize the `isToggleOn` state variable to `false`.\n* Use an object, `style`, to hold the styles for individual components and their states.\n* Return a `<button>` that alters the component's `isToggledOn` when its `onClick` event is fired and determine the appearance of the content based on `isToggleOn`, applying the appropriate CSS rules from the `style` object.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": "",
|
||||
"code": "function Toggle(props) {\r\n const [isToggleOn, setIsToggleOn] = React.useState(false);\r\n style = {\r\n on: {\r\n backgroundColor: 'green'\r\n },\r\n off: {\r\n backgroundColor: 'grey'\r\n }\r\n };\r\n\r\n return (\r\n <button onClick={() => setIsToggleOn(!isToggleOn)} style={isToggleOn ? style.on : style.off}>\r\n {isToggleOn ? 'ON' : 'OFF'}\r\n </button>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(<Toggle />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "a497d9a55df0c12999f08d0cd9c169b5d5c8e4f56d80ab7550ef539dc73f4fcf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Tooltip",
|
||||
"title": "Tooltip",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "Tooltip.md",
|
||||
"text": "Renders a tooltip component.\n\n* Use the `React.useState()` hook to create the `show` variable and initialize it to `false`.\n* Return a `<div>` element that contains the `<div>` that will be the tooltip and the `children` passed to the component.\n* Handle the `onMouseEnter` and `onMouseLeave` methods, by altering the value of the `show` variable.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": ".tooltip {\r\n position: relative;\r\n background: rgba(0, 0, 0, 0.7);\r\n color: white;\r\n visibility: hidden;\r\n padding: 5px;\r\n border-radius: 5px;\r\n}\r\n.tooltip-arrow {\r\n position: absolute;\r\n top: 100%;\r\n left: 50%;\r\n border-width: 5px;\r\n border-style: solid;\r\n border-color: rgba(0, 0, 0, 0.7) transparent transparent;\r\n}",
|
||||
"code": "function Tooltip({ children, text, ...rest }) {\r\n const [show, setShow] = React.useState(false);\r\n\r\n return (\r\n <div>\r\n <div className=\"tooltip\" style={show ? { visibility: 'visible' } : {}}>\r\n {text}\r\n <span className=\"tooltip-arrow\" />\r\n </div>\r\n <div {...rest} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\r\n {children}\r\n </div>\r\n </div>\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <Tooltip text=\"Simple tooltip\">\r\n <button>Hover me!</button>\r\n </Tooltip>,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"visual",
|
||||
"state",
|
||||
"children",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "fc1cec890de96741cba636008aa04af5f63cb127f4fedec61e31394c02fd4dbf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "TreeView",
|
||||
"title": "TreeView",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "TreeView.md",
|
||||
"text": "Renders a tree view of a JSON object or array with collapsible content.\n\n* Use object destructuring to set defaults for certain props.\n* Use the value of the `toggled` prop to determine the initial state of the content (collapsed/expanded).\n* Use the `React.setState()` hook to create the `isToggled` state variable and give it the value of the `toggled` prop initially.\n* Return a `<div>` to wrap the contents of the component and the `<span>` element, used to alter the component's `isToggled` state.\n* Determine the appearance of the component, based on `isParentToggled`, `isToggled`, `name` and `Array.isArray()` on `data`.\n* For each child in `data`, determine if it is an object or array and recursively render a sub-tree.\n* Otherwise, render a `<p>` element with the appropriate style.\n\n",
|
||||
"codeBlocks": {
|
||||
"style": ".tree-element {\r\n margin: 0;\r\n position: relative;\r\n}\r\n\r\ndiv.tree-element:before {\r\n content: '';\r\n position: absolute;\r\n top: 24px;\r\n left: 1px;\r\n height: calc(100% - 48px);\r\n border-left: 1px solid gray;\r\n}\r\n\r\n.toggler {\r\n position: absolute;\r\n top: 10px;\r\n left: 0px;\r\n width: 0;\r\n height: 0;\r\n border-top: 4px solid transparent;\r\n border-bottom: 4px solid transparent;\r\n border-left: 5px solid gray;\r\n cursor: pointer;\r\n}\r\n\r\n.toggler.closed {\r\n transform: rotate(90deg);\r\n}\r\n\r\n.collapsed {\r\n display: none;\r\n}",
|
||||
"code": "function TreeView({\r\n data,\r\n toggled = true,\r\n name = null,\r\n isLast = true,\r\n isChildElement = false,\r\n isParentToggled = true\r\n}) {\r\n const [isToggled, setIsToggled] = React.useState(toggled);\r\n\r\n return (\r\n <div\r\n style={{ marginLeft: isChildElement ? 16 : 4 + 'px' }}\r\n className={isParentToggled ? 'tree-element' : 'tree-element collapsed'}\r\n >\r\n <span\r\n className={isToggled ? 'toggler' : 'toggler closed'}\r\n onClick={() => setIsToggled(!isToggled)}\r\n />\r\n {name ? <strong> {name}: </strong> : <span> </span>}\r\n {Array.isArray(data) ? '[' : '{'}\r\n {!isToggled && '...'}\r\n {Object.keys(data).map((v, i, a) =>\r\n typeof data[v] == 'object' ? (\r\n <TreeView\r\n data={data[v]}\r\n isLast={i === a.length - 1}\r\n name={Array.isArray(data) ? null : v}\r\n isChildElement\r\n isParentToggled={isParentToggled && isToggled}\r\n />\r\n ) : (\r\n <p\r\n style={{ marginLeft: 16 + 'px' }}\r\n className={isToggled ? 'tree-element' : 'tree-element collapsed'}\r\n >\r\n {Array.isArray(data) ? '' : <strong>{v}: </strong>}\r\n {data[v]}\r\n {i === a.length - 1 ? '' : ','}\r\n </p>\r\n )\r\n )}\r\n {Array.isArray(data) ? ']' : '}'}\r\n {!isLast ? ',' : ''}\r\n </div>\r\n );\r\n}",
|
||||
"example": "let data = {\r\n lorem: {\r\n ipsum: 'dolor sit',\r\n amet: {\r\n consectetur: 'adipiscing',\r\n elit: [\r\n 'duis',\r\n 'vitae',\r\n {\r\n semper: 'orci'\r\n },\r\n {\r\n est: 'sed ornare'\r\n },\r\n 'etiam',\r\n ['laoreet', 'tincidunt'],\r\n ['vestibulum', 'ante']\r\n ]\r\n },\r\n ipsum: 'primis'\r\n }\r\n};\r\nReactDOM.render(<TreeView data={data} name=\"data\" />, document.getElementById('root'));"
|
||||
},
|
||||
"tags": [
|
||||
"object",
|
||||
"visual",
|
||||
"state",
|
||||
"recursion",
|
||||
"advanced"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "4002acde51d7ecf13830a102da4ca83b702b50c9603834d8902dfba859d955e4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "UncontrolledInput",
|
||||
"title": "UncontrolledInput",
|
||||
"type": "snippet",
|
||||
"attributes": {
|
||||
"fileName": "UncontrolledInput.md",
|
||||
"text": "Renders an `<input>` 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 `<input>` element.\n* Render an `<input>` 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": {
|
||||
"style": "",
|
||||
"code": "function UncontrolledInput({ callback, type = 'text', disabled = false, readOnly = false, placeholder = '' }) {\r\n return (\r\n <input\r\n type={type}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n placeholder={placeholder}\r\n onChange={({ target: { value } }) => callback(value)}\r\n />\r\n );\r\n}",
|
||||
"example": "ReactDOM.render(\r\n <UncontrolledInput type=\"text\" placeholder=\"Insert some text here...\" callback={val => console.log(val)} />,\r\n document.getElementById('root')\r\n);"
|
||||
},
|
||||
"tags": [
|
||||
"input",
|
||||
"beginner"
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"hash": "7ea3169ca176a8bf0610f34082363a18811d16451519b62776ffae9f0175820c"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"specification": "http://jsonapi.org/format/",
|
||||
"type": "snippetArray"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user