Merge branch 'master' into feature/prettier-formatting
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,3 @@
|
||||
# Created by https://www.gitignore.io/api/node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@ -74,6 +71,3 @@ typings/
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/node
|
||||
81
CONTRIBUTING.md
Normal file
81
CONTRIBUTING.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
**30 seconds of React** is a community effort, so feel free to contribute in any way you can. Every contribution helps!
|
||||
|
||||
Here's what you can do to help:
|
||||
|
||||
- Submit [pull requests](https://github.com/30-seconds/30-seconds-of-react/pulls) with snippets that you have created (see below for guidelines).
|
||||
- [Open issues](https://github.com/30-seconds/30-seconds-of-react/issues/new) for things you want to see added or modified.
|
||||
- Be part of the discussion by helping out with [existing issues](https://github.com/30-seconds/30-seconds-of-react/issues).
|
||||
- Fix typos in existing snippets, improve snippet descriptions and explanations or provide better examples.
|
||||
|
||||
### Snippet submission and Pull request guidelines
|
||||
|
||||
- **DO NOT MODIFY THE README.md or index.html FILES!** Make changes to individual snippet files. **Travis CI** will automatically build the `README.md` and `index.html` files when your pull request is merged.
|
||||
- **Snippet filenames** must correspond to the title of the snippet. For example, if your snippet is titled `### AwesomeComponent` the filename should be `AwesomeComponent.md`.
|
||||
- Use `TitleCase`, not `camelCase`, `kebab-case` or `snake_case` when naming components.
|
||||
- Avoid capitalization of words, except if the whole word is capitalized (e.g. `URL` should be capitalized in the filename and the snippet title).
|
||||
- **Snippet titles** should be the same as the name of the component that is present in the snippet.
|
||||
- All snippet titles must be prefixed with `###` and be at the very first line of your snippet.
|
||||
- Snippet titles must be unique (although if you cannot find a better title, just add some placeholder at the end of the filename and title and we will figure it out).
|
||||
- Follow snippet titles with an empty line.
|
||||
- **Snippet descriptions** must be short and to the point. Try to explain *what* the snippet does and *how* the snippet works and what Javascript/React features are used. Remember to include what functions you are using and why.
|
||||
- Follow snippet descriptions with an empty line.
|
||||
- **Snippet code** must be enclosed inside ` ```jsx ` and ` ``` `.
|
||||
- If your snippet is accompanied by CSS code, enclose it inside ` ```css ` and ` ``` ` and present it before the JS/JSX code.
|
||||
- Remember to start your snippet's code on a new line below the opening backticks.
|
||||
- Use standard function notation to define your component. For example `function MyComponent(props) { ... }`.
|
||||
- Do not write components using classes, use [React Hooks](https://reactjs.org/docs/hooks-intro.html) instead.
|
||||
- Please use Javascript [Semi-Standard Style](https://github.com/Flet/semistandard).
|
||||
- Try to keep your snippets' code short and to the point. Use modern techniques and features. Make sure to test your code before submitting.
|
||||
- All snippets must be followed by one (more if necessary) test case after the code, in a new block enclosed inside ` ```jsx ` and ` ``` `. The syntax for this is `ReactDOM.render(<MyComponent />, document.getElementById("root"));`. Use multiline examples only if necessary.
|
||||
- Try to make your component name unique, so that it does not conflict with existing snippets.
|
||||
- Snippets should be as brief as possible, without sacrificing functionality. If your snippet seems longer than most, you can still submit it, and we can help you shorten it or figure out ways to improve it.
|
||||
- Snippets *should* solve real-world problems, no matter how simple.
|
||||
- Snippets *should* be abstract enough to be applied to different scenarios.
|
||||
- You can start creating a new snippet, by using the [snippet template](snippet-template.md) to format your snippets.
|
||||
|
||||
### Additional guidelines and conventions regarding snippets
|
||||
|
||||
- When describing snippets, refer to methods, using their full name. For example, use `Array.prototype.reduce()`, instead of `reduce()`.
|
||||
- When using React Hooks, refer to the specific hooks with their full names, such as `React.useState()` and `React.useEffect()`.
|
||||
- When using `React.useState()`, try matching the name of the state variable to the function that sets it. For example, use `[isShown, setIsShown]` instead of `[isShown, setShown]`.
|
||||
- When using `React.useEffect()`, only return a function if you have to clean up. In that case, name that function `cleanup()`.
|
||||
- Destructure your component's `props` whenever possible. If any of your props take default parameters, specify their default values in the destructured object.
|
||||
- If your snippet uses recursion, explain the base cases.
|
||||
- Always use `function MyComponent(props)` or `function MyComponent({ ... })` for function definitions.
|
||||
- Use variables only when necessary. Prefer `const` when the values are not altered after assignment, otherwise, use `let`. Avoid using `var`.
|
||||
- Use `camelCase` for function and variable names, if they consist of more than one word.
|
||||
- Use `TitleCase` for component names.
|
||||
- Try to give meaningful names to variables. For example use `letter`, instead of `lt`. Some exceptions to convention are:
|
||||
- `arr` for arrays (usually as the snippet function's argument).
|
||||
- `str` for strings.
|
||||
- `num` or `n` for a numeric value (usually as the snippet function's argument).
|
||||
- `el` for DOM elements (usually as the snippet function's argument).
|
||||
- `val` or `v` for value (usually when iterating a list, mapping, sorting etc.).
|
||||
- `acc` for accumulators in `Array.prototype.reduce()`.
|
||||
- `(a,b)` for the two values compared when using `Array.prototype.sort()`.
|
||||
- `i` for indexes.
|
||||
- `fn` for function arguments.
|
||||
- `nums` for arrays of numbers.
|
||||
- Use `()` if your function takes no arguments.
|
||||
- Use `_` if an argument inside some function (e.g. `Array.prototype.reduce()`) is not used anywhere in your code.
|
||||
- Specify default parameters for arguments, if necessary. It is preferred to put default parameters last unless you have a pretty good reason not to.
|
||||
- If your snippet's function takes variadic arguments, use `...args` or `...rest` (although in certain cases, it might be needed to use a different name).
|
||||
- Always use soft tabs (2 spaces), never hard tabs.
|
||||
- Omit curly braces (`{` and `}`) whenever possible.
|
||||
- Always use single quotes for string literals. Use template literals, instead, if necessary.
|
||||
- When rendering JSX, use double quotes, instead of single quotes.
|
||||
- Prefer using `Array` methods whenever possible.
|
||||
- Prefer `Array.prototype.concat()` instead of `Array.prototype.push()` when working with `Array.prototype.reduce()`.
|
||||
- Use strict equality checking (`===` and `!==` instead of `==` and `!=`), unless you specifically have reason not to.
|
||||
- Prefer using the ternary operator (`condition ? trueResult : falseResult`) instead of `if else` statements whenever possible.
|
||||
- Avoid nesting ternary operators (but you can do it if you feel like you should).
|
||||
- You should define multiple variables (e.g. `const x = 0, y = 0`) on the same line whenever possible.
|
||||
- Do not use trailing or leading underscores in variable names.
|
||||
- Use dot notation (`object.property`) for object properties, when possible. Use bracket notation (`object[variable]`) when accessing object properties using a variable.
|
||||
- Use arrow functions as much as possible, except when you can't.
|
||||
- Use semicolons whenever necessary.
|
||||
- Leave a single space after a comma (`,`) character.
|
||||
- Try to strike a balance between readability, brevity, and performance.
|
||||
- Never use `eval()`. Your snippet will be disqualified immediately.
|
||||
349
data/snippet_data.json
Normal file
349
data/snippet_data.json
Normal file
@ -0,0 +1,349 @@
|
||||
[
|
||||
{
|
||||
"name": "AutoLink.md",
|
||||
"title": "AutoLink",
|
||||
"text": "Renders a string as plaintext, with URLs converted to appropriate `<a>` elements.\n\nUse `String.prototype.split()` and `String.prototype.match()` with a regular expression to find URLs in a string.\nReturn 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 (\n <a href={url.startsWith(\"http\") ? url : `http://${url}`}>{url}</a>\n );\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\nUse the `React.setState()` hook to create the `active` state variable and give it a value of `0` (index of the first item).\nUse an object, `style`, to hold the styles for the individual components.\nUse the `React.setEffect()` hook to update the value of `active` to the index of the next item, using `setTimeout`.\nDestructure `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.\nRender 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\nUse the `React.setState()` hook to create the `isCollapsed` state variable with an initial value of `props.collapsed`.\nUse an object, `style`, to hold the styles for individual components and their states.\nUse 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`.\nDetermine the appearance of the content, based on `isCollapsed` and apply the appropriate CSS rules from the `style` object.\nFinally, 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\n style={style.buttonStyle}\n onClick={() => setIsCollapsed(!isCollapsed)}\n >\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\nUse object destructuring to set defaults for the `hours`, `minutes` and `seconds` props.\nUse 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.\nCreate a method `tick`, that updates the value of `time` based on the current value (i.e. decreasing the time by one second).\nIf `paused` or `over` is `true`, `tick` will return immediately.\nCreate a method `reset`, that resets all state variables to their initial states.\nUse 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.\nUse 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.\nIf `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)\n 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\n .toString()\n .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)}>\n {paused ? \"Resume\" : \"Pause\"}\n </button>\n <button onClick={() => reset()}>Restart</button>\n </div>\n );\n}\n```",
|
||||
"```jsx\nReactDOM.render(\n <CountDown hours=\"1\" minutes=\"45\" />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 2,
|
||||
"tags": [
|
||||
"visual",
|
||||
"state"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "DataList.md",
|
||||
"title": "DataList",
|
||||
"text": "Renders a list of elements from an array of primitives.\n\nUse the value of the `isOrdered` prop to conditionally render a `<ol>` or `<ul>` list.\nUse `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.\nOmit 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) => (\n <li key={`${i}_${val}`}>{val}</li>\n ));\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\nRender a `<table>` element with two columns (`ID` and `Value`).\nUse `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(\n <DataTable data={people} />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"array"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "FileDrop.md",
|
||||
"title": "FileDrop",
|
||||
"text": "Renders a file drag and drop component for a single file.\n\nCreate a ref called `dropRef` for this component.\nUse 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\nCreate the `handleDrag`, `handleDragIn`, `handleDragOut` and `handleDrop` methods to handle drag and drop functionality, bind them to the component's context.\nEach 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`.\nReturn an appropriately styled `<div>` and use `drag` and `filename` to determine its contents and style. \nFinally, bind the `ref` of the created `<div>` to `dropRef`.\n\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={\n drag ? \"filedrop drag\" : filename ? \"filedrop ready\" : \"filedrop\"\n }\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\nUse object destructuring to set defaults for certain attributes of the `<input>` element.\nRender 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\nUse 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`.\nUse the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.\nUse 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(\n <LimitedTextarea limit={32} value='Hello!' />,\n document.getElementById('root')\n);\n```"
|
||||
],
|
||||
"expertise": 0,
|
||||
"tags": [
|
||||
"input",
|
||||
"state",
|
||||
"effect"
|
||||
],
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"name": "LimitedWordTextarea.md",
|
||||
"title": "LimitedWordTextarea",
|
||||
"text": "Renders a textarea component with a word limit.\n\nUse the `React.useState()` hook to create the `content` and `wordCount` state variables and set their values to `value` and `0` respectively.\nCreate 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`.\nIf the afforementioned `length` exceeds the `limit`, trim the input, otherwise return the raw input, updating `content` and `wordCount` accordingly in both cases.\nUse the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.\nUse 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\nDestructure the component's props, use `email`, `subject` and `body` to create a `<a>` element with an appropriate `href` attribute.\nRender 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 || \"\"}`}>\n {props.children}\n </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\nUse `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`.\nRender a `<table>` element with a set of columns equal to the amount of values in `propertyNames`.\nUse `Array.prototype.map` to render each value in the `propertyNames` array as a `<th>` element.\nUse `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>{propertyNames.map(val => <th key={`h_${val}`}>{val}</th>)}</tr>\n </thead>\n <tbody>\n {filteredData.map((val, i) => (\n <tr key={`i_${i}`}>\n {propertyNames.map(p => <td key={`i_${i}_${p}`}>{val[p]}</td>)}\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": "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\nUse `React.setState()` to create a `data` state variable and set its initial value equal to the `options` prop.\nCreate a function `toggle` that is used to toggle the `checked` to update the `data` state variable and call the `onChange` callbac passed via the component's props.\nRender a `<ul>` element and use `Array.prototype.map()` to map the `data` state variable to individual `<li>` elements with `<input>` elements as their children.\nEach `<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) \n 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\n key={item.label}\n style={style.itemStyle}\n onClick={() => toggle(item)}\n >\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\nUse the `React.useState()` hook to create the `shown` state variable and set its value to `false`.\nUse 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\n type={shown ? \"text\" : \"password\"}\n value={value}\n onChange={() => {}}\n />\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\nUse object destructuring to set defaults for certain attributes of the `<select>` element.\nRender 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.\nUse 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]) => <option selected={selected === value}value={value}>{text}</option>)}\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": "StarRating.md",
|
||||
"title": "StarRating",
|
||||
"text": "Renders a star rating component.\n\nDefine a component, called `Star` that will render each individual star with the appropriate appearance, based on the parent component's state.\nIn the `StarRating` component, use the `React.setState()` 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`.\nCreate a method, `hoverOver`, that updates `selected` and `rating` according to the provided `event`.\nCreate 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. \nFinally, 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(\n typeof props.rating == \"number\" ? props.rating : 0\n );\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={() =>\n setRating(event.target.getAttribute(\"star-id\") || this.state.rating)\n }\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": "Tab",
|
||||
"text": "Renders a tabbed menu and view component.\n\nDefine a `TabItem` component, pass it to the `Tab` and remove unnecessary nodes expect for `TabItem` by identifying the function's name in `props.children`.\nUse the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`. \nUse `Array.prototype.map` on the collected nodes to render the `tab-menu` and `tab-view`. \nDefine `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\n onClick={() => changeTab(index)}\n className={bindIndex === index ? \"focus\" : \"\"}\n >\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\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\nUse object destructuring to set defaults for certain attributes of the `<textarea>` element.\nRender 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 ({ callback, cols = 20, rows = 2, disabled = false, readOnly = false, placeholder='' }) {\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\nUse the `React.useState()` hook to initialize the `ticker` state variable to `0`.\nDefine two methods, `tick` and `reset`, that will periodically increment `timer` based on `interval` and reset `interval` respectively.\nReturn 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) \n setTicker(ticker + 1);\n else \n 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\nUse the `React.useState()` to initialize the `isToggleOn` state variable to `false`.\nUse an object, `style`, to hold the styles for individual components and their states.\nReturn 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\n onClick={() => setIsToggleOn(!isToggleOn)}\n style={isToggleOn ? style.on : style.off}\n >\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\nUse the `React.useState()` hook to create the `show` variable and initialize it to `false`.\nReturn a `<div>` element that contains the `<div>` that will be the tooltip and the `children` passed to the component.\nHandle 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\n {...rest}\n onMouseEnter={() => setShow(true)}\n onMouseLeave={() => setShow(false)}\n >\n {children}\n </div>\n </div>\n );\n}\n```",
|
||||
"```jsx\n ReactDOM.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\nUse object destructuring to set defaults for certain props. \nUse the value of the `toggled` prop to determine the initial state of the content (collapsed/expanded).\nUse the `React.setState()` hook to create the `isToggled` state variable and give it the value of the `toggled` prop initially.\nReturn a `<div>` to wrap the contents of the component and the `<span>` element, used to alter the component's `isToggled` state.\nDetermine the appearance of the component, based on `isParentToggled`, `isToggled`, `name` and `Array.isArray()` on `data`. \nFor each child in `data`, determine if it is an object or array and recursively render a sub-tree.\nOtherwise, 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(\n (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": []
|
||||
}
|
||||
]
|
||||
593
package-lock.json
generated
Normal file
593
package-lock.json
generated
Normal file
@ -0,0 +1,593 @@
|
||||
{
|
||||
"name": "30-seconds-of-react",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "1.9.3"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "1.0.3"
|
||||
}
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
|
||||
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
|
||||
"dev": true
|
||||
},
|
||||
"caller-callsite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||
"integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsites": "2.0.0"
|
||||
}
|
||||
},
|
||||
"caller-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
|
||||
"integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caller-callsite": "2.0.0"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||
"integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "3.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"supports-color": "5.5.0"
|
||||
}
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz",
|
||||
"integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"import-fresh": "2.0.0",
|
||||
"is-directory": "0.3.1",
|
||||
"js-yaml": "3.12.1",
|
||||
"parse-json": "4.0.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nice-try": "1.0.5",
|
||||
"path-key": "2.0.1",
|
||||
"semver": "5.6.0",
|
||||
"shebang-command": "1.2.0",
|
||||
"which": "1.3.1"
|
||||
}
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "1.4.0"
|
||||
}
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-arrayish": "0.2.1"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
},
|
||||
"execa": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
|
||||
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "6.0.5",
|
||||
"get-stream": "4.1.0",
|
||||
"is-stream": "1.1.0",
|
||||
"npm-run-path": "2.0.2",
|
||||
"p-finally": "1.0.0",
|
||||
"signal-exit": "3.0.2",
|
||||
"strip-eof": "1.0.0"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "3.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.15",
|
||||
"jsonfile": "4.0.0",
|
||||
"universalify": "0.1.2"
|
||||
}
|
||||
},
|
||||
"get-stdin": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
|
||||
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
|
||||
"dev": true
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
||||
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pump": "3.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.15",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
|
||||
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
|
||||
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
|
||||
"dev": true
|
||||
},
|
||||
"husky": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz",
|
||||
"integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cosmiconfig": "5.0.7",
|
||||
"execa": "1.0.0",
|
||||
"find-up": "3.0.0",
|
||||
"get-stdin": "6.0.0",
|
||||
"is-ci": "2.0.0",
|
||||
"pkg-dir": "3.0.0",
|
||||
"please-upgrade-node": "3.1.1",
|
||||
"read-pkg": "4.0.1",
|
||||
"run-node": "1.0.0",
|
||||
"slash": "2.0.0"
|
||||
}
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
|
||||
"integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caller-path": "2.0.0",
|
||||
"resolve-from": "3.0.0"
|
||||
}
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
||||
"dev": true
|
||||
},
|
||||
"is-builtin-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
|
||||
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"builtin-modules": "1.1.1"
|
||||
}
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ci-info": "2.0.0"
|
||||
}
|
||||
},
|
||||
"is-directory": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
|
||||
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz",
|
||||
"integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "1.0.10",
|
||||
"esprima": "4.0.1"
|
||||
}
|
||||
},
|
||||
"json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.15"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "3.0.0",
|
||||
"path-exists": "3.0.0"
|
||||
}
|
||||
},
|
||||
"markdown-builder": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-builder/-/markdown-builder-0.9.0.tgz",
|
||||
"integrity": "sha512-UovCyEEzMeKE7l88fbOk9SIJkOG7KXkg+TdudN8rvOtCtBO5uu1X27HSnM7LS/xH+vaShJLGpkBcYYcojWNx/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"husky": "1.3.1"
|
||||
}
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
|
||||
"integrity": "sha512-YcMnjqeoUckXTPKZSAsPjUPLxH85XotbpqK3w4RyCwdFQSU5FxxBys8buehkSfg0j9fKvV1hn7O0+8reEgkAiw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hosted-git-info": "2.7.1",
|
||||
"is-builtin-module": "1.0.0",
|
||||
"semver": "5.6.0",
|
||||
"validate-npm-package-license": "3.0.4"
|
||||
}
|
||||
},
|
||||
"npm-run-path": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "2.0.1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
|
||||
"dev": true
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
|
||||
"integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-try": "2.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "2.1.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
|
||||
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"error-ex": "1.3.2",
|
||||
"json-parse-better-errors": "1.0.2"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
|
||||
"dev": true
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
||||
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
|
||||
"dev": true
|
||||
},
|
||||
"pkg-dir": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
|
||||
"integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"find-up": "3.0.0"
|
||||
}
|
||||
},
|
||||
"please-upgrade-node": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz",
|
||||
"integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver-compare": "1.0.0"
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.1",
|
||||
"once": "1.4.0"
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
|
||||
"integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-package-data": "2.4.2",
|
||||
"parse-json": "4.0.0",
|
||||
"pify": "3.0.0"
|
||||
}
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
|
||||
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
|
||||
"dev": true
|
||||
},
|
||||
"run-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
|
||||
"integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
|
||||
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver-compare": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "1.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||
"dev": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
|
||||
"dev": true
|
||||
},
|
||||
"slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true
|
||||
},
|
||||
"spdx-correct": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
|
||||
"integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-expression-parse": "3.0.0",
|
||||
"spdx-license-ids": "3.0.3"
|
||||
}
|
||||
},
|
||||
"spdx-exceptions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
|
||||
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
|
||||
"dev": true
|
||||
},
|
||||
"spdx-expression-parse": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
|
||||
"integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-exceptions": "2.2.0",
|
||||
"spdx-license-ids": "3.0.3"
|
||||
}
|
||||
},
|
||||
"spdx-license-ids": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
|
||||
"integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==",
|
||||
"dev": true
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-eof": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "3.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-correct": "3.1.0",
|
||||
"spdx-expression-parse": "3.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "2.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
25
package.json
25
package.json
@ -1,11 +1,19 @@
|
||||
{
|
||||
"name": "30-seconds-of-react",
|
||||
"version": "1.0.0",
|
||||
"repository": "git@github.com:30-seconds/30-seconds-of-react.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/30-seconds/30-seconds-of-react.git"
|
||||
},
|
||||
"scripts": {
|
||||
"extractor": "node ./scripts/extract.js",
|
||||
"builder": "node ./scripts/build.js",
|
||||
"prettier": "prettier **/*.{js,json,css,md}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chalk": "^2.4.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"markdown-builder": "^0.9.0",
|
||||
"husky": "^1.1.2",
|
||||
"lint-staged": "^7.3.0",
|
||||
"prettier": "^1.14.3"
|
||||
@ -23,5 +31,18 @@
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
},
|
||||
"description": "Curated collection of useful React snippets that you can understand in 30 seconds or less.",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"react",
|
||||
"snippets",
|
||||
"javascript"
|
||||
],
|
||||
"author": "30-seconds (30secondsofcode@gmail.com)",
|
||||
"license": "CC0-1.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/30-seconds/30-seconds-of-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/30-seconds/30-seconds-of-react#readme"
|
||||
}
|
||||
|
||||
104
scripts/build.js
Normal file
104
scripts/build.js
Normal file
@ -0,0 +1,104 @@
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const chalk = require("chalk");
|
||||
const util = require("./util.js");
|
||||
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 STATIC_PARTS_PATH = "./static-parts";
|
||||
|
||||
let startPart = "";
|
||||
let endPart = "";
|
||||
let output = "";
|
||||
|
||||
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`;
|
||||
|
||||
console.time("Builder");
|
||||
|
||||
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"
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(`${chalk.red("ERROR!")} During static part loading: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// add static part for start
|
||||
output += `${startPart}\n`;
|
||||
|
||||
const snippetsInTag = {};
|
||||
|
||||
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('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
|
||||
output += `\n${endPart}\n`;
|
||||
|
||||
fs.writeFileSync("README.md", output);
|
||||
} catch (err) {
|
||||
console.log(`${chalk.red("ERROR!")} During README generation: ${err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`${chalk.green("SUCCESS!")} README file generated!`);
|
||||
console.timeEnd("Builder");
|
||||
43
scripts/extract.js
Normal file
43
scripts/extract.js
Normal file
@ -0,0 +1,43 @@
|
||||
const fs = require("fs-extra")
|
||||
const path = require("path")
|
||||
const chalk = require("chalk")
|
||||
const {
|
||||
attempt,
|
||||
readSnippets,
|
||||
getCodeBlocks,
|
||||
getSection,
|
||||
getTitle,
|
||||
getTextualContent
|
||||
} = require("./util");
|
||||
|
||||
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() !== "")
|
||||
|
||||
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!`);
|
||||
console.timeEnd("Extractor");
|
||||
95
scripts/util.js
Normal file
95
scripts/util.js
Normal file
@ -0,0 +1,95 @@
|
||||
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
|
||||
};
|
||||
@ -6,23 +6,24 @@ Explain briefly how the snippet works.
|
||||
|
||||
```jsx
|
||||
function ComponentName(props) {
|
||||
const [state, setState] = React.useState(null);
|
||||
React.useEffect(() => {
|
||||
setState(0);
|
||||
});
|
||||
return <div>{props}</div>;
|
||||
};
|
||||
```
|
||||
<!-- OR -->
|
||||
```jsx
|
||||
class ComponentName extends React.Component {
|
||||
constructor(props){}
|
||||
render(){
|
||||
return <div>{props}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<ComponentName />, mountNode);
|
||||
ReactDOM.render(<ComponentName />, document.getElementById("root"));
|
||||
```
|
||||
|
||||
<!-- OPTIONAL -->
|
||||
#### Notes:
|
||||
* Things to remember when using this
|
||||
* Other options that might be less appealing or have lower compatibility
|
||||
* Common mistakes and issues
|
||||
|
||||
<!-- tags: (separate each by a comma) -->
|
||||
|
||||
<!-- expertise: (0,1,2,3) -->
|
||||
|
||||
38
snippets/AutoLink.md
Normal file
38
snippets/AutoLink.md
Normal file
@ -0,0 +1,38 @@
|
||||
### AutoLink
|
||||
|
||||
Renders a string as plaintext, with URLs converted to appropriate `<a>` elements.
|
||||
|
||||
Use `String.prototype.split()` and `String.prototype.match()` with a regular expression to find URLs in a string.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
function AutoLink({ text }) {
|
||||
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;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{text.split(delimiter).map(word => {
|
||||
let match = word.match(delimiter);
|
||||
if (match) {
|
||||
let url = match[0];
|
||||
return (
|
||||
<a href={url.startsWith("http") ? url : `http://${url}`}>{url}</a>
|
||||
);
|
||||
}
|
||||
return word;
|
||||
})}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<AutoLink text='foo bar baz http://example.org bar' />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: string,fragment,regexp -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
67
snippets/Carousel.md
Normal file
67
snippets/Carousel.md
Normal file
@ -0,0 +1,67 @@
|
||||
### Carousel
|
||||
|
||||
Renders a carousel component.
|
||||
|
||||
Use the `React.setState()` hook to create the `active` state variable and give it a value of `0` (index of the first item).
|
||||
Use an object, `style`, to hold the styles for the individual components.
|
||||
Use the `React.setEffect()` hook to update the value of `active` to the index of the next item, using `setTimeout`.
|
||||
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.
|
||||
Render the carousel items using `React.cloneElement()` and pass down rest `props` along with the computed styles.
|
||||
|
||||
```jsx
|
||||
function Carousel(props) {
|
||||
const [active, setActive] = React.useState(0);
|
||||
let scrollInterval = null;
|
||||
const style = {
|
||||
carousel: {
|
||||
position: "relative"
|
||||
},
|
||||
carouselItem: {
|
||||
position: "absolute",
|
||||
visibility: "hidden"
|
||||
},
|
||||
visible: {
|
||||
visibility: "visible"
|
||||
}
|
||||
};
|
||||
React.useEffect(() => {
|
||||
scrollInterval = setTimeout(() => {
|
||||
const { carouselItems } = props;
|
||||
setActive((active + 1) % carouselItems.length);
|
||||
}, 2000);
|
||||
});
|
||||
const { carouselItems, ...rest } = props;
|
||||
return (
|
||||
<div style={style.carousel}>
|
||||
{carouselItems.map((item, index) => {
|
||||
const activeStyle = active === index ? style.visible : {};
|
||||
return React.cloneElement(item, {
|
||||
...rest,
|
||||
style: {
|
||||
...style.carouselItem,
|
||||
...activeStyle
|
||||
}
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Carousel
|
||||
carouselItems={[
|
||||
<div>carousel item 1</div>,
|
||||
<div>carousel item 2</div>,
|
||||
<div>carousel item 3</div>
|
||||
]}
|
||||
/>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: visual,children,state,effect -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
|
||||
@ -2,55 +2,46 @@
|
||||
|
||||
Renders a component with collapsible content.
|
||||
|
||||
Use the value of the `collapsed` prop to determine the initial state of the content (collapsed/expanded).
|
||||
Set the `state` of the component to the value of the `collapsed` prop (cast to a boolean value) and bind the `toggleCollapse` method to the component's context.
|
||||
Use the `React.setState()` hook to create the `isCollapsed` state variable with an initial value of `props.collapsed`.
|
||||
Use an object, `style`, to hold the styles for individual components and their states.
|
||||
Create a method, `toggleCollapse`, which uses `Component.prototype.setState` to change the component's `state` from collapsed to expanded and vice versa.
|
||||
In the `render()` method, use a `<div>` to wrap both the `<button>` that alters the component's `state` and the content of the component, passed down via `props.children`.
|
||||
Determine the appearance of the content, based on `state.collapsed` and apply the appropriate CSS rules from the `style` object.
|
||||
Finally, update the value of the `aria-expanded` attribute based on `state.collapsed` to make the component accessible.
|
||||
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`.
|
||||
Determine the appearance of the content, based on `isCollapsed` and apply the appropriate CSS rules from the `style` object.
|
||||
Finally, update the value of the `aria-expanded` attribute based on `isCollapsed` to make the component accessible.
|
||||
|
||||
```jsx
|
||||
class Collapse extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
collapsed: !!props.collapsed
|
||||
};
|
||||
this.style = {
|
||||
collapsed: {
|
||||
display: 'none'
|
||||
},
|
||||
expanded: {
|
||||
display: 'block'
|
||||
},
|
||||
buttonStyle: {
|
||||
display: 'block',
|
||||
width: '100%'
|
||||
}
|
||||
};
|
||||
this.toggleCollapse = this.toggleCollapse.bind(this);
|
||||
}
|
||||
|
||||
toggleCollapse() {
|
||||
this.setState(state => ({ collapsed: !state.collapsed }));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button style={this.style.buttonStyle} onClick={this.toggleCollapse}>
|
||||
Show/Hide Content
|
||||
</button>
|
||||
<div
|
||||
style= {this.state.collapsed ? this.style.collapsed : this.style.expanded}
|
||||
aria-expanded = {this.state.collapsed}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
function Collapse(props) {
|
||||
const [isCollapsed, setIsCollapsed] = React.useState(props.collapsed);
|
||||
|
||||
const style = {
|
||||
collapsed: {
|
||||
display: "none"
|
||||
},
|
||||
expanded: {
|
||||
display: "block"
|
||||
},
|
||||
buttonStyle: {
|
||||
display: "block",
|
||||
width: "100%"
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
style={style.buttonStyle}
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
>
|
||||
{isCollapsed ? "Show" : "Hide"} content
|
||||
</button>
|
||||
<div
|
||||
className="collapse-content"
|
||||
style={isCollapsed ? style.collapsed : style.expanded}
|
||||
aria-expanded={isCollapsed}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@ -64,6 +55,6 @@ ReactDOM.render(
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: visual,children,state,class -->
|
||||
<!-- tags: visual,children,state -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
|
||||
89
snippets/CountDown.md
Normal file
89
snippets/CountDown.md
Normal file
@ -0,0 +1,89 @@
|
||||
### CountDown
|
||||
|
||||
Renders a countdown timer that prints a message when it reaches zero.
|
||||
|
||||
Use object destructuring to set defaults for the `hours`, `minutes` and `seconds` props.
|
||||
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.
|
||||
Create a method `tick`, that updates the value of `time` based on the current value (i.e. decreasing the time by one second).
|
||||
If `paused` or `over` is `true`, `tick` will return immediately.
|
||||
Create a method `reset`, that resets all state variables to their initial states.
|
||||
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.
|
||||
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.
|
||||
If `over` is `true`, the timer will display a message instead of the value of `time`.
|
||||
|
||||
```jsx
|
||||
function CountDown({ hours = 0, minutes = 0, seconds = 0 }) {
|
||||
const [paused, setPaused] = React.useState(false);
|
||||
const [over, setOver] = React.useState(false);
|
||||
const [time, setTime] = React.useState({
|
||||
hours: parseInt(hours),
|
||||
minutes: parseInt(minutes),
|
||||
seconds: parseInt(seconds)
|
||||
});
|
||||
|
||||
const tick = () => {
|
||||
if (paused || over) return;
|
||||
if (time.hours == 0 && time.minutes == 0 && time.seconds == 0)
|
||||
setOver(true);
|
||||
else if (time.minutes == 0 && time.seconds == 0)
|
||||
setTime({
|
||||
hours: time.hours - 1,
|
||||
minutes: 59,
|
||||
seconds: 59
|
||||
});
|
||||
else if (time.seconds == 0)
|
||||
setTime({
|
||||
hours: time.hours,
|
||||
minutes: time.minutes - 1,
|
||||
seconds: 59
|
||||
});
|
||||
else
|
||||
setTime({
|
||||
hours: time.hours,
|
||||
minutes: time.minutes,
|
||||
seconds: time.seconds - 1
|
||||
});
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setTime({
|
||||
hours: parseInt(hours),
|
||||
minutes: parseInt(minutes),
|
||||
seconds: parseInt(seconds)
|
||||
});
|
||||
setPaused(false);
|
||||
setOver(false);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
let timerID = setInterval(() => tick(), 1000);
|
||||
return () => clearInterval(timerID);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{`${time.hours
|
||||
.toString()
|
||||
.padStart(2, "0")}:${time.minutes
|
||||
.toString()
|
||||
.padStart(2, "0")}:${time.seconds.toString().padStart(2, "0")}`}</p>
|
||||
<div>{over ? "Time's up!" : ""}</div>
|
||||
<button onClick={() => setPaused(!paused)}>
|
||||
{paused ? "Resume" : "Pause"}
|
||||
</button>
|
||||
<button onClick={() => reset()}>Restart</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<CountDown hours="1" minutes="45" />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: visual,state -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
26
snippets/DataList.md
Normal file
26
snippets/DataList.md
Normal file
@ -0,0 +1,26 @@
|
||||
### DataList
|
||||
|
||||
Renders a list of elements from an array of primitives.
|
||||
|
||||
Use the value of the `isOrdered` prop to conditionally render a `<ol>` or `<ul>` list.
|
||||
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.
|
||||
Omit the `isOrdered` prop to render a `<ul>` list by default.
|
||||
|
||||
```jsx
|
||||
function DataList({ isOrdered, data }) {
|
||||
const list = data.map((val, i) => (
|
||||
<li key={`${i}_${val}`}>{val}</li>
|
||||
));
|
||||
return isOrdered ? <ol>{list}</ol> : <ul>{list}</ul>;
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
const names = ['John', 'Paul', 'Mary'];
|
||||
ReactDOM.render(<DataList data={names}/>, document.getElementById('root'));
|
||||
ReactDOM.render(<DataList data={names} isOrdered/>, document.getElementById('root'));
|
||||
```
|
||||
|
||||
<!-- tags: array -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
41
snippets/DataTable.md
Normal file
41
snippets/DataTable.md
Normal file
@ -0,0 +1,41 @@
|
||||
### DataTable
|
||||
|
||||
Renders a table with rows dynamically created from an array of primitives.
|
||||
|
||||
Render a `<table>` element with two columns (`ID` and `Value`).
|
||||
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.
|
||||
|
||||
```jsx
|
||||
function DataTable({ data }) {
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((val, i) =>
|
||||
<tr key={`${i}_${val}`}>
|
||||
<td>{i}</td>
|
||||
<td>{val}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
const people = ['John', 'Jesse'];
|
||||
ReactDOM.render(
|
||||
<DataTable data={people} />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: array -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
106
snippets/FileDrop.md
Normal file
106
snippets/FileDrop.md
Normal file
@ -0,0 +1,106 @@
|
||||
### FileDrop
|
||||
|
||||
Renders a file drag and drop component for a single file.
|
||||
|
||||
Create a ref called `dropRef` for this component.
|
||||
Use the `React.useState()` hook to create the `drag` and `filename` variables, initialized to `false` and `''` respectively.
|
||||
The 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.
|
||||
|
||||
Create the `handleDrag`, `handleDragIn`, `handleDragOut` and `handleDrop` methods to handle drag and drop functionality, bind them to the component's context.
|
||||
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.
|
||||
`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`.
|
||||
Return an appropriately styled `<div>` and use `drag` and `filename` to determine its contents and style.
|
||||
Finally, bind the `ref` of the created `<div>` to `dropRef`.
|
||||
|
||||
|
||||
```css
|
||||
.filedrop {
|
||||
min-height: 120px;
|
||||
border: 3px solid #D3D3D3;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
padding: 32px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.filedrop.drag {
|
||||
border: 3px dashed #1E90FF;
|
||||
}
|
||||
|
||||
.filedrop.ready {
|
||||
border: 3px solid #32CD32;
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
function FileDrop(props) {
|
||||
const [drag, setDrag] = React.useState(false);
|
||||
const [filename, setFilename] = React.useState('');
|
||||
let dropRef = React.createRef();
|
||||
let dragCounter = 0;
|
||||
|
||||
const handleDrag = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragIn = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter++;
|
||||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) setDrag(true);
|
||||
};
|
||||
|
||||
const handleDragOut = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter--;
|
||||
if (dragCounter === 0) setDrag(false);
|
||||
};
|
||||
|
||||
const handleDrop = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDrag(false);
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
props.handleDrop(e.dataTransfer.files[0]);
|
||||
setFilename(e.dataTransfer.files[0].name);
|
||||
e.dataTransfer.clearData();
|
||||
dragCounter = 0;
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
let div = dropRef.current;
|
||||
div.addEventListener("dragenter", handleDragIn);
|
||||
div.addEventListener("dragleave", handleDragOut);
|
||||
div.addEventListener("dragover", handleDrag);
|
||||
div.addEventListener("drop", handleDrop);
|
||||
return function cleanup() {
|
||||
div.removeEventListener("dragenter", handleDragIn);
|
||||
div.removeEventListener("dragleave", handleDragOut);
|
||||
div.removeEventListener("dragover", handleDrag);
|
||||
div.removeEventListener("drop", handleDrop);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={dropRef}
|
||||
className={
|
||||
drag ? "filedrop drag" : filename ? "filedrop ready" : "filedrop"
|
||||
}
|
||||
>
|
||||
{filename && !drag ? <div>{filename}</div> : <div>Drop files here!</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<FileDrop handleDrop={console.log}/>, document.getElementById('root'));
|
||||
```
|
||||
|
||||
<!-- tags: visual,input,state,effect -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
31
snippets/Input.md
Normal file
31
snippets/Input.md
Normal file
@ -0,0 +1,31 @@
|
||||
### Input
|
||||
|
||||
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 Input ({ callback, type = 'text', disabled = false, readOnly = false, placeholder = '' }) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
placeholder={placeholder}
|
||||
onChange={({ target: { value } }) => callback(value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Input type='text' placeholder='Insert some text here...' callback={(val) => console.log(val)}/>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: input -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
47
snippets/LimitedTextarea.md
Normal file
47
snippets/LimitedTextarea.md
Normal file
@ -0,0 +1,47 @@
|
||||
### LimitedTextarea
|
||||
|
||||
Renders a textarea component with a character limit.
|
||||
|
||||
Use the `React.useState()` hook to create the `content` state variable and set its value to `value`.
|
||||
Create a method `setFormattedContent`, which trims the content of the input if it's longer than `limit`.
|
||||
Use the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.
|
||||
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`.
|
||||
|
||||
```jsx
|
||||
function LimitedTextarea({ rows, cols, value, limit }) {
|
||||
const [content, setContent] = React.useState(value);
|
||||
|
||||
const setFormattedContent = text => {
|
||||
text.length > limit ? setContent(text.slice(0, limit)) : setContent(text);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setFormattedContent(content);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
rows={rows}
|
||||
cols={cols}
|
||||
onChange={event => setFormattedContent(event.target.value)}
|
||||
value={content}
|
||||
/>
|
||||
<p>
|
||||
{content.length}/{limit}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<LimitedTextarea limit={32} value='Hello!' />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
<!-- tags: input,state,effect -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
|
||||
61
snippets/LimitedWordTextarea.md
Normal file
61
snippets/LimitedWordTextarea.md
Normal file
@ -0,0 +1,61 @@
|
||||
### LimitedWordTextarea
|
||||
|
||||
Renders a textarea component with a word limit.
|
||||
|
||||
Use the `React.useState()` hook to create the `content` and `wordCount` state variables and set their values to `value` and `0` respectively.
|
||||
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`.
|
||||
If the afforementioned `length` exceeds the `limit`, trim the input, otherwise return the raw input, updating `content` and `wordCount` accordingly in both cases.
|
||||
Use the `React.useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable.
|
||||
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`.
|
||||
|
||||
```jsx
|
||||
function LimitedWordTextarea({ rows, cols, value, limit }) {
|
||||
const [content, setContent] = React.useState(value);
|
||||
const [wordCount, setWordCount] = React.useState(0);
|
||||
|
||||
const setFormattedContent = text => {
|
||||
let words = text.split(" ");
|
||||
if (words.filter(Boolean).length > limit) {
|
||||
setContent(
|
||||
text
|
||||
.split(" ")
|
||||
.slice(0, limit)
|
||||
.join(" ")
|
||||
);
|
||||
setWordCount(limit);
|
||||
} else {
|
||||
setContent(text);
|
||||
setWordCount(words.filter(Boolean).length);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setFormattedContent(content);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
rows={rows}
|
||||
cols={cols}
|
||||
onChange={event => setFormattedContent(event.target.value)}
|
||||
value={content}
|
||||
/>
|
||||
<p>
|
||||
{wordCount}/{limit}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<LimitedWordTextArea limit={5} value='Hello there!' />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
<!-- tags: input,state,effect -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
|
||||
29
snippets/Mailto.md
Normal file
29
snippets/Mailto.md
Normal file
@ -0,0 +1,29 @@
|
||||
### 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>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Mailto email="foo@bar.baz" subject="Hello" body="Hello world!">
|
||||
Mail me!
|
||||
</Mailto>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: visual -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
51
snippets/MappedTable.md
Normal file
51
snippets/MappedTable.md
Normal file
@ -0,0 +1,51 @@
|
||||
### MappedTable
|
||||
|
||||
Renders a table with rows dynamically created from an array of objects and a list of property names.
|
||||
|
||||
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`.
|
||||
Render a `<table>` element with a set of columns equal to the amount of values in `propertyNames`.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
function MappedTable({ data, propertyNames }) {
|
||||
let filteredData = data.map(v =>
|
||||
Object.keys(v)
|
||||
.filter(k => propertyNames.includes(k))
|
||||
.reduce((acc, key) => ((acc[key] = v[key]), acc), {})
|
||||
);
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>{propertyNames.map(val => <th key={`h_${val}`}>{val}</th>)}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredData.map((val, i) => (
|
||||
<tr key={`i_${i}`}>
|
||||
{propertyNames.map(p => <td key={`i_${i}_${p}`}>{val[p]}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
const people = [
|
||||
{ name: 'John', surname: 'Smith', age: 42 },
|
||||
{ name: 'Adam', surname: 'Smith', gender: 'male' }
|
||||
];
|
||||
const propertyNames = ['name', 'surname', 'age'];
|
||||
ReactDOM.render(
|
||||
<MappedTable data={people} propertyNames={propertyNames} />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
#### 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 -->
|
||||
69
snippets/MultiselectCheckbox.md
Normal file
69
snippets/MultiselectCheckbox.md
Normal file
@ -0,0 +1,69 @@
|
||||
### MultiselectCheckbox
|
||||
|
||||
Renders a checkbox list that uses a callback function to pass its selected value/values to the parent component.
|
||||
|
||||
Use `React.setState()` to create a `data` state variable and set its initial value equal to the `options` prop.
|
||||
Create a function `toggle` that is used to toggle the `checked` to update the `data` state variable and call the `onChange` callbac passed via the component's props.
|
||||
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.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
const style = {
|
||||
listContainer: {
|
||||
listStyle: 'none',
|
||||
paddingLeft: 0
|
||||
},
|
||||
itemStyle: {
|
||||
cursor: 'pointer',
|
||||
padding: 5
|
||||
}
|
||||
};
|
||||
|
||||
function MultiselectCheckbox ({ options, onChange }) {
|
||||
const [data, setData] = React.useState(options);
|
||||
|
||||
const toggle = (item) => {
|
||||
data.map((_, key) => {
|
||||
if (data[key].label === item.label)
|
||||
data[key].checked = !item.checked;
|
||||
});
|
||||
setData([...data]);
|
||||
onChange(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul style={style.listContainer}>
|
||||
{data.map(item => {
|
||||
return (
|
||||
<li
|
||||
key={item.label}
|
||||
style={style.itemStyle}
|
||||
onClick={() => toggle(item)}
|
||||
>
|
||||
<input readOnly type='checkbox' checked={item.checked || false} />
|
||||
{item.label}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
const options = [{ label: "Item One" }, { label: "Item Two" }];
|
||||
|
||||
ReactDOM.render(
|
||||
<MultiselectCheckbox
|
||||
options={options}
|
||||
onChange={data => {
|
||||
console.log(data);
|
||||
}}
|
||||
/>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: input,state,array -->
|
||||
|
||||
<!-- expertise: 1 -->
|
||||
32
snippets/PasswordRevealer.md
Normal file
32
snippets/PasswordRevealer.md
Normal file
@ -0,0 +1,32 @@
|
||||
### PasswordRevealer
|
||||
|
||||
Renders a password input field with a reveal button.
|
||||
|
||||
Use the `React.useState()` hook to create the `shown` state variable and set its value to `false`.
|
||||
Use a`<div>` to wrap both the`<input>` and the `<button>` element that toggles the type of the input field between `"text"` and `"password"`.
|
||||
|
||||
```jsx
|
||||
function PasswordRevealer({ value }) {
|
||||
const [shown, setShown] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type={shown ? "text" : "password"}
|
||||
value={value}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<button onClick={() => setShown(!shown)}>Show/Hide</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<PasswordRevealer />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
<!--tags: input,state -->
|
||||
|
||||
<!--expertise: 0 -->
|
||||
|
||||
38
snippets/Select.md
Normal file
38
snippets/Select.md
Normal file
@ -0,0 +1,38 @@
|
||||
### Select
|
||||
|
||||
Renders a `<select>` 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 `<select>` element.
|
||||
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.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
function Select ({ values, callback, disabled = false, readonly = false, selected }) {
|
||||
return (
|
||||
<select
|
||||
disabled={disabled}
|
||||
readOnly={readonly}
|
||||
onChange={({ target : { value } }) => callback(value)}
|
||||
>
|
||||
{values.map(([value, text]) => <option selected={selected === value}value={value}>{text}</option>)}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
let choices = [
|
||||
['grapefruit', 'Grapefruit'],
|
||||
['lime', 'Lime'],
|
||||
['coconut', 'Coconut'],
|
||||
['mango', 'Mango']
|
||||
];
|
||||
ReactDOM.render(
|
||||
<Select values={choices} selected='lime' callback={(val) => console.log(val)}/>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: input -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
@ -2,68 +2,50 @@
|
||||
|
||||
Renders a star rating component.
|
||||
|
||||
Use and IIFE to define a functional component, called `Star` that will render each individual star with the appropriate appearance, based on the parent component's `state` and return the class component `StarRating`.
|
||||
Use the value of the `rating` prop to determine if a valid rating is supplied and store it in `state.rating` (or `0` if invalid or not supplied).
|
||||
Initialize `state.selection` to `0`.
|
||||
Create two methods, `hoverOver` and `setRating`, that take an event as argument and update `state.selected` and `state.rating` according to it, bind them both to the component's context.
|
||||
In the `render()` method, 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 `state.selection` to `0`, the `onClick` event to set
|
||||
the `state.rating` and the `onMouseOver` event to set `state.selection` to the `star-id` attribute of the `event.target` respectively.
|
||||
Define a component, called `Star` that will render each individual star with the appropriate appearance, based on the parent component's state.
|
||||
In the `StarRating` component, use the `React.setState()` 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`.
|
||||
Create a method, `hoverOver`, that updates `selected` and `rating` according to the provided `event`.
|
||||
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.
|
||||
Finally, pass the appropriate values to each `<Star>` component (`starId` and `marked`).
|
||||
|
||||
```jsx
|
||||
const StarRating = (function() {
|
||||
function Star({ marked, starId }) {
|
||||
return (
|
||||
<span star-id={starId} style={{ color: '#ff9933' }} role='button'>
|
||||
{marked ? '\u2605' : '\u2606'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
function Star({ marked, starId }) {
|
||||
return (
|
||||
<span star-id={starId} style={{ color: "#ff9933" }} role="button">
|
||||
{marked ? "\u2605" : "\u2606"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return class StarRating extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
rating: typeof props.rating == 'number' ? props.rating : 0,
|
||||
selection: 0
|
||||
};
|
||||
this.hoverOver = this.hoverOver.bind(this);
|
||||
this.hoverOut = this.hoverOver.bind(this, null);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
hoverOver(event) {
|
||||
let val = 0;
|
||||
if (event && event.target && event.target.getAttribute('star-id'))
|
||||
val = event.target.getAttribute('star-id');
|
||||
this.setState(state => ({ selection: val }));
|
||||
}
|
||||
handleClick(event) {
|
||||
const val = event.target.getAttribute('star-id') || this.state.rating;
|
||||
this.setState(state => ({ rating: val }));
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onMouseOut={this.hoverOut}
|
||||
onClick={this.handleClick}
|
||||
onMouseOver={this.hoverOver}
|
||||
>
|
||||
{Array.from({ length: 5 }, (v, i) => (
|
||||
<Star
|
||||
starId={i+1}
|
||||
key={`star_${i+1} `}
|
||||
marked={
|
||||
this.state.selection
|
||||
? this.state.selection >= i+1
|
||||
: this.state.rating >= i+1
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function StarRating(props) {
|
||||
const [rating, setRating] = React.useState(
|
||||
typeof props.rating == "number" ? props.rating : 0
|
||||
);
|
||||
const [selection, setSelection] = React.useState(0);
|
||||
const hoverOver = event => {
|
||||
let val = 0;
|
||||
if (event && event.target && event.target.getAttribute("star-id"))
|
||||
val = event.target.getAttribute("star-id");
|
||||
setSelection(val);
|
||||
};
|
||||
})();
|
||||
return (
|
||||
<div
|
||||
onMouseOut={() => hoverOver(null)}
|
||||
onClick={() =>
|
||||
setRating(event.target.getAttribute("star-id") || this.state.rating)
|
||||
}
|
||||
onMouseOver={hoverOver}
|
||||
>
|
||||
{Array.from({ length: 5 }, (v, i) => (
|
||||
<Star
|
||||
starId={i + 1}
|
||||
key={`star_${i + 1} `}
|
||||
marked={selection ? selection >= i + 1 : rating >= i + 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
@ -71,6 +53,6 @@ ReactDOM.render(<StarRating/>, document.getElementById('root'));
|
||||
ReactDOM.render(<StarRating rating={2} />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
<!-- tags: visual,children,input,state,class -->
|
||||
<!-- tags: visual,children,input,state -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
|
||||
82
snippets/Tabs.md
Normal file
82
snippets/Tabs.md
Normal file
@ -0,0 +1,82 @@
|
||||
### Tab
|
||||
Renders a tabbed menu and view component.
|
||||
|
||||
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`.
|
||||
Use the `React.useState()` hook to initialize the value of the `bindIndex` state variable to `props.defaultIndex`.
|
||||
Use `Array.prototype.map` on the collected nodes to render the `tab-menu` and `tab-view`.
|
||||
Define `changeTab`, which will be executed when clicking a `<button>` from the `tab-menu`.
|
||||
`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`.
|
||||
|
||||
```css
|
||||
.tab-menu > button {
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
border: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
background: none;
|
||||
}
|
||||
.tab-menu > button.focus {
|
||||
border-bottom: 2px solid #007BEF;
|
||||
}
|
||||
.tab-menu > button:hover {
|
||||
border-bottom: 2px solid #007BEF;
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
function TabItem(props) {
|
||||
return <div {...props} />;
|
||||
}
|
||||
|
||||
function Tabs(props) {
|
||||
const [bindIndex, setBindIndex] = React.useState(props.defaultIndex);
|
||||
const changeTab = newIndex => {
|
||||
if (typeof props.onTabClick === "function") props.onTabClick(newIndex);
|
||||
setBindIndex(newIndex);
|
||||
};
|
||||
const items = props.children.filter(item => item.type.name === "TabItem");
|
||||
|
||||
return (
|
||||
<div className="wrapper">
|
||||
<div className="tab-menu">
|
||||
{items.map(({ props: { index, label } }) => (
|
||||
<button
|
||||
onClick={() => changeTab(index)}
|
||||
className={bindIndex === index ? "focus" : ""}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="tab-view">
|
||||
{items.map(({ props }) => (
|
||||
<div
|
||||
{...props}
|
||||
className="tab-view_item"
|
||||
key={props.index}
|
||||
style={{ display: bindIndex === props.index ? "block" : "none" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Tabs defaultIndex="1" onTabClick={console.log}>
|
||||
<TabItem label="A" index="1">
|
||||
Lorem ipsum
|
||||
</TabItem>
|
||||
<TabItem label="B" index="2">
|
||||
Dolor sit amet
|
||||
</TabItem>
|
||||
</Tabs>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
<!-- tags: visual,state,children -->
|
||||
|
||||
<!-- expertise: 1 -->
|
||||
32
snippets/TextArea.md
Normal file
32
snippets/TextArea.md
Normal file
@ -0,0 +1,32 @@
|
||||
### TextArea
|
||||
|
||||
Renders a `<textarea>` 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 `<textarea>` element.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
function TextArea ({ callback, cols = 20, rows = 2, disabled = false, readOnly = false, placeholder='' }) {
|
||||
return (
|
||||
<textarea
|
||||
cols={cols}
|
||||
rows={rows}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
placeholder={placeholder}
|
||||
onChange={({ target : { value } }) => callback(value)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<TextArea placeholder='Insert some text here...' callback={(val) => console.log(val)}/>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: input -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
45
snippets/Ticker.md
Normal file
45
snippets/Ticker.md
Normal file
@ -0,0 +1,45 @@
|
||||
### Ticker
|
||||
|
||||
Renders a ticker component.
|
||||
|
||||
Use the `React.useState()` hook to initialize the `ticker` state variable to `0`.
|
||||
Define two methods, `tick` and `reset`, that will periodically increment `timer` based on `interval` and reset `interval` respectively.
|
||||
Return a `<div>` with two `<button>` elements, each of which calls `tick` and `reset` respectively.
|
||||
|
||||
```jsx
|
||||
function Ticker(props) {
|
||||
const [ticker, setTicker] = React.useState(0);
|
||||
let interval = null;
|
||||
|
||||
const tick = () => {
|
||||
reset();
|
||||
interval = setInterval(() => {
|
||||
if (ticker < props.times)
|
||||
setTicker(ticker + 1);
|
||||
else
|
||||
clearInterval(interval);
|
||||
}, props.interval);
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setTicker(0);
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span style={{ fontSize: 100 }}>{this.state.ticker}</span>
|
||||
<button onClick={this.tick}>Tick!</button>
|
||||
<button onClick={this.reset}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Ticker times={5} interval={1000} />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
<!-- tags: visual,state -->
|
||||
|
||||
<!-- expertise: 1 -->
|
||||
38
snippets/Toggle.md
Normal file
38
snippets/Toggle.md
Normal file
@ -0,0 +1,38 @@
|
||||
### Toggle
|
||||
|
||||
Renders a toggle component.
|
||||
|
||||
Use the `React.useState()` to initialize the `isToggleOn` state variable to `false`.
|
||||
Use an object, `style`, to hold the styles for individual components and their states.
|
||||
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.
|
||||
|
||||
```jsx
|
||||
function Toggle(props) {
|
||||
const [isToggleOn, setIsToggleOn] = React.useState(false);
|
||||
style = {
|
||||
on: {
|
||||
backgroundColor: "green"
|
||||
},
|
||||
off: {
|
||||
backgroundColor: "grey"
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setIsToggleOn(!isToggleOn)}
|
||||
style={isToggleOn ? style.on : style.off}
|
||||
>
|
||||
{isToggleOn ? "ON" : "OFF"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(<Toggle />, document.getElementById('root'));
|
||||
```
|
||||
|
||||
<!-- tags: visual,state -->
|
||||
|
||||
<!-- expertise: 0 -->
|
||||
60
snippets/Tooltip.md
Normal file
60
snippets/Tooltip.md
Normal file
@ -0,0 +1,60 @@
|
||||
### Tooltip
|
||||
|
||||
Renders a tooltip component.
|
||||
|
||||
Use the `React.useState()` hook to create the `show` variable and initialize it to `false`.
|
||||
Return a `<div>` element that contains the `<div>` that will be the tooltip and the `children` passed to the component.
|
||||
Handle the `onMouseEnter` and `onMouseLeave` methods, by altering the value of the `show` variable.
|
||||
|
||||
```css
|
||||
.tooltip {
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
visibility: hidden;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, 0.7) transparent transparent;
|
||||
}
|
||||
```
|
||||
```jsx
|
||||
function Tooltip({ children, text, ...rest }) {
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="tooltip" style={show ? { visibility: "visible" } : {}}>
|
||||
{text}
|
||||
<span className="tooltip-arrow" />
|
||||
</div>
|
||||
<div
|
||||
{...rest}
|
||||
onMouseEnter={() => setShow(true)}
|
||||
onMouseLeave={() => setShow(false)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
ReactDOM.render(
|
||||
<Tooltip text='Simple tooltip'>
|
||||
<button>Hover me!</button>
|
||||
</Tooltip>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
```
|
||||
|
||||
<!-- tags: visual,state,children -->
|
||||
|
||||
<!-- expertise: 1 -->
|
||||
128
snippets/TreeView.md
Normal file
128
snippets/TreeView.md
Normal file
@ -0,0 +1,128 @@
|
||||
### TreeView
|
||||
|
||||
Renders a tree view of a JSON object or array with collapsible content.
|
||||
|
||||
Use object destructuring to set defaults for certain props.
|
||||
Use the value of the `toggled` prop to determine the initial state of the content (collapsed/expanded).
|
||||
Use the `React.setState()` hook to create the `isToggled` state variable and give it the value of the `toggled` prop initially.
|
||||
Return a `<div>` to wrap the contents of the component and the `<span>` element, used to alter the component's `isToggled` state.
|
||||
Determine the appearance of the component, based on `isParentToggled`, `isToggled`, `name` and `Array.isArray()` on `data`.
|
||||
For each child in `data`, determine if it is an object or array and recursively render a sub-tree.
|
||||
Otherwise, render a `<p>` element with the appropriate style.
|
||||
|
||||
```css
|
||||
.tree-element {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.tree-element:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 1px;
|
||||
height: calc(100% - 48px);
|
||||
border-left: 1px solid gray;
|
||||
}
|
||||
|
||||
.toggler {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 0px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
border-left: 5px solid gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggler.closed {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
function TreeView({
|
||||
data,
|
||||
toggled = true,
|
||||
name = null,
|
||||
isLast = true,
|
||||
isChildElement = false,
|
||||
isParentToggled = true
|
||||
}) {
|
||||
const [isToggled, setIsToggled] = React.useState(toggled);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ marginLeft: isChildElement ? 16 : 4 + "px" }}
|
||||
className={isParentToggled ? "tree-element" : "tree-element collapsed"}
|
||||
>
|
||||
<span
|
||||
className={isToggled ? "toggler" : "toggler closed"}
|
||||
onClick={() => setIsToggled(!isToggled)}
|
||||
/>
|
||||
{name ? <strong> {name}: </strong> : <span> </span>}
|
||||
{Array.isArray(data) ? "[" : "{"}
|
||||
{!isToggled && "..."}
|
||||
{Object.keys(data).map(
|
||||
(v, i, a) =>
|
||||
typeof data[v] == "object" ? (
|
||||
<TreeView
|
||||
data={data[v]}
|
||||
isLast={i === a.length - 1}
|
||||
name={Array.isArray(data) ? null : v}
|
||||
isChildElement
|
||||
isParentToggled={isParentToggled && isToggled}
|
||||
/>
|
||||
) : (
|
||||
<p
|
||||
style={{ marginLeft: 16 + "px" }}
|
||||
className={isToggled ? "tree-element" : "tree-element collapsed"}
|
||||
>
|
||||
{Array.isArray(data) ? "" : <strong>{v}: </strong>}
|
||||
{data[v]}
|
||||
{i === a.length - 1 ? "" : ","}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
{Array.isArray(data) ? "]" : "}"}
|
||||
{!isLast ? "," : ""}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
let data = {
|
||||
lorem: {
|
||||
ipsum: "dolor sit",
|
||||
amet: {
|
||||
consectetur: "adipiscing",
|
||||
elit: [
|
||||
"duis",
|
||||
"vitae",
|
||||
{
|
||||
semper: "orci"
|
||||
},
|
||||
{
|
||||
est: "sed ornare"
|
||||
},
|
||||
"etiam",
|
||||
["laoreet", "tincidunt"],
|
||||
["vestibulum", "ante"]
|
||||
]
|
||||
},
|
||||
ipsum: "primis"
|
||||
}
|
||||
};
|
||||
ReactDOM.render(<TreeView data={data} name='data'/>, document.getElementById("root"));
|
||||
```
|
||||
|
||||
<!-- tags: object,visual,state,recursion -->
|
||||
|
||||
<!-- expertise: 2 -->
|
||||
141
snippets_archive/ModalDialog.md
Normal file
141
snippets_archive/ModalDialog.md
Normal file
@ -0,0 +1,141 @@
|
||||
### ModalDialog
|
||||
|
||||
Renders a dialog component in a modal, controllable through events.
|
||||
To use the component, import `ModalDialog` only once and then display it using `ModalDialog.show()`, passing the JSX templates and data as parameters.
|
||||
|
||||
Define `modalHandler`, a method that will handle showing the modal dialog, set `state` to the default values initially and bind the `close` and `modalClick` methods to the component's context.
|
||||
Define `close` and `modalClick` to toggle the visibility of the modal dialog, based on `state.closeOnClick`.
|
||||
Use the CustomEvent API to listen for `modal` events, that can be dispatched from the `static` `show()` method, handle listeners appropriately from `componentDidMount` and `componentWillUnmount`.
|
||||
|
||||
The `show()` method accepts an argument, that should contain three parameters:
|
||||
* `title`, a string for the dialog's title
|
||||
* `closeOnClick`, `true` if the modal should close on click or `false` if it should only close when clicking the *X* button
|
||||
* `content`, which is the JSX content to be rendered inside the dialog
|
||||
|
||||
Finally, in the `render()` method, use a `<div>` to wrap everything and render the modal dialog with the content passed to `show()`.
|
||||
|
||||
```css
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.dialog {
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialog-title {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 0.5px solid #c3c3c3;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.dialog-close {
|
||||
font-size: 32px;
|
||||
color: #c3c3c3;
|
||||
cursor: pointer;
|
||||
transform: rotate(45deg);
|
||||
user-select: none;
|
||||
}
|
||||
.dialog-close:hover {
|
||||
color: red;
|
||||
}
|
||||
.dialog-content {
|
||||
min-width: 300px;
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
class ModalDialog extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.modalHandler = (e) => {
|
||||
this.setState({
|
||||
data: e.detail.data,
|
||||
visible: true
|
||||
});
|
||||
};
|
||||
this.state = {
|
||||
data: {
|
||||
title: '',
|
||||
closeOnClick: false,
|
||||
content: ''
|
||||
},
|
||||
visible: false
|
||||
};
|
||||
this.close = this.close.bind(this);
|
||||
this.modalClick = this.modalClick.bind(this);
|
||||
}
|
||||
render() {
|
||||
return !this.state.visible ? null : <div className="modal" onClick={this.modalClick}>
|
||||
<div className="dialog">
|
||||
<div className="dialog-title">{ this.state.data.title }<span className="dialog-close" onClick={this.close}>+</span></div>
|
||||
<div className="dialog-content">
|
||||
{
|
||||
this.state.data.content
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
componentDidMount() {
|
||||
document.addEventListener('modal', this.modalHandler);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('modal', this.modalHandler);
|
||||
}
|
||||
close() {
|
||||
this.setState({
|
||||
visible: false,
|
||||
data: {
|
||||
title: '',
|
||||
closeOnClick: false,
|
||||
content: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
static show(data) {
|
||||
document.dispatchEvent(new CustomEvent('modal', {
|
||||
detail: {
|
||||
data
|
||||
}
|
||||
}));
|
||||
}
|
||||
modalClick() {
|
||||
if (this.state.data.closeOnClick) this.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
// add to render function
|
||||
<ModalDialog />
|
||||
|
||||
// every time you wanna call the dialog
|
||||
// content is a jsx element
|
||||
ModalDialog.show({
|
||||
title: 'Hello, world!',
|
||||
closeOnClick: true,
|
||||
content: <img src="https://github.com/30-seconds/30-seconds-of-react/blob/master/logo.png"/>
|
||||
});
|
||||
```
|
||||
|
||||
#### Notes:
|
||||
* This component includes a lot of CSS, which might conflict with other CSS in your project. It is recomended for the modal to be a direct child of the body tag.
|
||||
* A more up-to-date method with lower compatibility is to use [Portals](https://reactjs.org/docs/portals.html) in React 16+.
|
||||
|
||||
<!-- tags: visual,static,children,state,class -->
|
||||
|
||||
<!-- expertise: 1 -->
|
||||
5
static-parts/README-end.md
Normal file
5
static-parts/README-end.md
Normal file
@ -0,0 +1,5 @@
|
||||
-----
|
||||
|
||||
*This repository is a work in progress. If you want to contribute, please check the open issues to see where and how you can help out!*
|
||||
|
||||
*This README is built using [markdown-builder](https://github.com/30-seconds/markdown-builder).*
|
||||
38
static-parts/README-start.md
Normal file
38
static-parts/README-start.md
Normal file
@ -0,0 +1,38 @@
|
||||

|
||||
|
||||
# 30 seconds of React
|
||||
|
||||
> Curated collection of useful React snippets that you can understand in 30 seconds or less.
|
||||
|
||||
* Use <kbd>Ctrl</kbd> + <kbd>F</kbd> or <kbd>command</kbd> + <kbd>F</kbd> to search for a snippet.
|
||||
* Contributions welcome, please read the [contribution guide](CONTRIBUTING.md).
|
||||
* Snippets are written in React 16.8+, using hooks.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To import a snippet into your project, you must import `React` and copy-paste the component's JavaScript code like this:
|
||||
```js
|
||||
import React from 'react';
|
||||
|
||||
function MyComponent(props) {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
If there is any CSS related to your component, copy-paste it to a new file with the same name and the appropriate extension, then import it like this:
|
||||
```js
|
||||
import './MyComponent.css';
|
||||
```
|
||||
|
||||
To render your component, make sure there is a node with and id of `"root"` present in your element (preferrably a `<div>`) and that you have imported `ReactDOM`, like this:
|
||||
```js
|
||||
import ReactDOM from 'react-dom';
|
||||
```
|
||||
|
||||
#### Related projects
|
||||
|
||||
* [30 Seconds of Code](https://30secondsofcode.org)
|
||||
* [30 Seconds of CSS](https://30-seconds.github.io/30-seconds-of-css/)
|
||||
* [30 Seconds of Interviews](https://30secondsofinterviews.org/)
|
||||
|
||||
## Table of Contents
|
||||
Reference in New Issue
Block a user