Fix some snippets, start creating scripts

This commit is contained in:
Angelos Chalaris
2019-02-02 13:19:06 +02:00
parent a732143542
commit 9f3ff79952
8 changed files with 1012 additions and 2 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

301
data/snippet_data.json Normal file
View File

@ -0,0 +1,301 @@
[
{
"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",
"functional"
],
"notes": []
},
{
"name": "Carousel.md",
"title": "Carousel",
"text": "Renders a carousel component.\n\nInitially set `state.active` to `0` (index of the first item).\nUse an object, `style`, to hold the styles for the individual components.\nDefine a method, `setActiveItem`, which uses `this.setState` to change the state's `active` property to the index of the next item.\nDefine another method, `changeItem`, which is called by `setActiveItem` after updating the state each time and also when the component\nfirst renders (on `ComponentDidMount`).\nIn the `render()` method, destructure `state`, `style` and `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](https://reactjs.org/docs/react-api.html#cloneelement) and pass down rest\n`props` along with the computed styles.\n\n",
"codeBlocks": [
"```jsx\nclass Carousel extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n active: 0\n };\n this.scrollInterval = null;\n this.style = {\n carousel: {\n position: \"relative\"\n },\n carouselItem: {\n position: \"absolute\",\n visibility: \"hidden\"\n },\n visible: {\n visibility: \"visible\"\n }\n };\n }\n componentDidMount() {\n this.changeItem();\n }\n setActiveItem = () => {\n const { carouselItems } = this.props;\n this.setState(\n prevState => ({\n active: (prevState.active + 1) % carouselItems.length\n }),\n this.changeItem\n );\n };\n changeItem = () => {\n this.scrollInterval = setTimeout(this.setActiveItem, 2000);\n };\n render() {\n const { carouselItems, ...rest } = this.props;\n const { active } = this.state;\n const { visible, carousel, carouselItem } = this.style;\n return (\n <div style={carousel}>\n {carouselItems.map((item, index) => {\n const activeStyle = active === index ? visible : {};\n return React.cloneElement(item, {\n ...rest,\n style: {\n ...carouselItem,\n ...activeStyle\n }\n });\n })}\n </div>\n );\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",
"class"
],
"notes": []
},
{
"name": "Collapse.md",
"title": "Collapse",
"text": "Renders a component with collapsible content.\n\nUse the value of the `collapsed` prop to determine the initial state of the content (collapsed/expanded).\nSet 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.\nUse an object, `style`, to hold the styles for individual components and their states.\nCreate a method, `toggleCollapse`, which uses `Component.prototype.setState` to change the component's `state` from collapsed to expanded and vice versa.\nIn 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`.\nDetermine the appearance of the content, based on `state.collapsed` and apply the appropriate CSS rules from the `style` object.\nFinally, update the value of the `aria-expanded` attribute based on `state.collapsed` to make the component accessible.\n\n",
"codeBlocks": [
"```jsx\nclass Collapse extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n collapsed: !!props.collapsed\n };\n this.style = {\n collapsed: {\n display: 'none'\n },\n expanded: {\n display: 'block'\n },\n buttonStyle: {\n display: 'block',\n width: '100%'\n }\n };\n this.toggleCollapse = this.toggleCollapse.bind(this);\n }\n \n toggleCollapse() {\n this.setState(state => ({ collapsed: !state.collapsed }));\n }\n \n render() {\n return (\n <div>\n <button style={this.style.buttonStyle} onClick={this.toggleCollapse}>\n {this.state.collapsed ? 'Show' : 'Hide'} content\n </button>\n <div \n style= {this.state.collapsed ? this.style.collapsed : this.style.expanded} \n aria-expanded = {this.state.collapsed}\n >\n {this.props.children}\n </div>\n </div>\n );\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",
"class"
],
"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",
"functional"
],
"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",
"functional"
],
"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",
"functional"
],
"notes": []
},
{
"name": "LimitedTextarea.md",
"title": "LimitedTextarea",
"text": "Renders a textarea component with a character limit.\n\nUse the value of the `value` prop to determine the initial `state.content` and `state.characterCount` and the value of the `limit` props to determine the value of `state.limit`.\nCreate a method, `handleChange`, which trims the `event.target.value` data if necessary and uses `Component.prototype.setState` to update `state.content` and `state.characterCount`, and bind it to the component's context.\nIn the`render()` method, 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 the `handleChange` method.\n\n",
"codeBlocks": [
"```jsx\nclass LimitedTextarea extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n content: props.value,\n characterCount: props.value.length,\n limit: props.limit\n };\n this.handleChange = this.handleChange.bind(this);\n }\n \n handleChange(event) {\n let newContent = event.target.value;\n if(newContent.length >= this.state.limit) newContent = newContent.slice(0, this.state.limit);\n this.setState(state => ({ content: newContent, characterCount: newContent.length }));\n }\n render() {\n return (\n <div>\n <textarea \n rows={this.props.rows} \n cols={this.props.cols} \n onChange={this.handleChange} \n value={this.state.content}\n >\n </textarea>\n <p>{this.state.characterCount}/{this.props.limit}</p>\n </div>\n );\n }\n}\n```",
"```jsx\nReactDOM.render(\n <LimitedTextarea limit={32} value='Hello!' />,\n document.getElementById('root')\n);\n```"
],
"expertise": 0,
"tags": [
"input",
"state",
"class"
],
"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": [
"functional"
],
"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",
"functional"
],
"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,functional -->",
"<!-expertise: 1 -->"
]
},
{
"name": "ModalDialog.md",
"title": "ModalDialog",
"text": "Renders a dialog component in a modal, controllable through events. \nTo use the component, import `ModalDialog` only once and then display it using `ModalDialog.show()`, passing the JSX templates and data as parameters.\n\nDefine `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.\nDefine `close` and `modalClick` to toggle the visibility of the modal dialog, based on `state.closeOnClick`.\nUse the CustomEvent API to listen for `modal` events, that can be dispatched from the `static` `show()` method, handle listeners appropriately from `componentDidMount` and `componentWillUnmount`.\n\nThe `show()` method accepts an argument, that should contain three parameters:\n* `title`, a string for the dialog's title\n* `closeOnClick`, `true` if the modal should close on click or `false` if it should only close when clicking the *X* button\n* `content`, which is the JSX content to be rendered inside the dialog\n\nFinally, in the `render()` method, use a `<div>` to wrap everything and render the modal dialog with the content passed to `show()`.\n\n",
"codeBlocks": [
"```css\n .modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.6);\n z-index: 9998;\n display: flex;\n justify-content: center;\n align-items: center;\n }\n .dialog {\n background-color: white;\n border-radius: 5px;\n overflow: hidden;\n }\n .dialog-title {\n box-sizing: border-box;\n width: 100%;\n height: 48px;\n padding: 0 16px;\n border-bottom: 0.5px solid #c3c3c3;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n .dialog-close {\n font-size: 32px;\n color: #c3c3c3;\n cursor: pointer;\n transform: rotate(45deg);\n user-select: none;\n }\n .dialog-close:hover {\n color: red;\n }\n .dialog-content {\n min-width: 300px;\n }\n```",
"```jsx\nclass ModalDialog extends React.Component {\n constructor() {\n super();\n this.modalHandler = (e) => {\n this.setState({\n data: e.detail.data,\n visible: true\n });\n };\n this.state = {\n data: {\n title: '',\n closeOnClick: false,\n content: ''\n },\n visible: false\n };\n this.close = this.close.bind(this);\n this.modalClick = this.modalClick.bind(this);\n }\n render() {\n return !this.state.visible ? null : <div className=\"modal\" onClick={this.modalClick}>\n <div className=\"dialog\">\n <div className=\"dialog-title\">{ this.state.data.title }<span className=\"dialog-close\" onClick={this.close}>+</span></div>\n <div className=\"dialog-content\">\n {\n this.state.data.content\n }\n </div>\n </div>\n </div>\n }\n componentDidMount() {\n document.addEventListener('modal', this.modalHandler);\n }\n componentWillUnmount() {\n document.removeEventListener('modal', this.modalHandler);\n }\n close() {\n this.setState({\n visible: false,\n data: {\n title: '',\n closeOnClick: false,\n content: ''\n }\n });\n }\n static show(data) {\n document.dispatchEvent(new CustomEvent('modal', {\n detail: {\n data\n }\n }));\n }\n modalClick() {\n if (this.state.data.closeOnClick) this.close();\n }\n}\n```",
"```jsx\n// add to render function\n<ModalDialog />\n\n// every time you wanna call the dialog\n// content is a jsx element\nModalDialog.show({\n title: 'Hello, world!',\n closeOnClick: true,\n content: <img src=\"https://github.com/30-seconds/30-seconds-of-react/blob/master/logo.png\"/>\n}); \n```"
],
"expertise": 1,
"tags": [
"visual",
"static",
"children",
"state",
"class"
],
"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 -->"
]
},
{
"name": "PasswordRevealer.md",
"title": "PasswordRevealer",
"text": "Renders a password input field with a reveal button.\n\nInitially set `state.shown` to `false` to ensure that the password is not shown by default.\nCreate a method, `toggleShown`, which uses `Component.prototype.setState` to change the input's state from shown to hidden and vice versa, bind it to the component's context.\nIn the`render()` method, use a`<div>` to wrap both the`<input>` and the `<button>` element that toggles the type of the input field.\nFinally, bind the `<button>`'s `onClick` event to the `toggleShown` method.\n\n",
"codeBlocks": [
"```jsx\nclass PasswordRevealer extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n shown: false\n };\n this.toggleShown = this.toggleShown.bind(this);\n }\n\n toggleShown() {\n this.setState(state => ({ shown: !state.shown }));\n }\n\n render() {\n return (\n <div>\n <input\n type={this.state.shown ? 'text' : 'password'}\n value={this.props.value}\n />\n <button onClick={this.toggleShown}>Show/Hide</button>\n </div>\n );\n }\n}\n```",
"```jsx\nReactDOM.render(<PasswordRevealer />, document.getElementById('root'));\n```"
],
"expertise": 0,
"tags": [
"input",
"state",
"class"
],
"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",
"functional"
],
"notes": []
},
{
"name": "StarRating.md",
"title": "StarRating",
"text": "Renders a star rating component.\n\nUse 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`.\nUse 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).\nInitialize `state.selection` to `0`.\nCreate 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.\nIn 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\nthe `state.rating` and the `onMouseOver` event to set `state.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\nconst StarRating = (function() {\n function Star({ marked, starId }) {\n return (\n <span star-id={starId} style={{ color: '#ff9933' }} role='button'>\n {marked ? '\\u2605' : '\\u2606'}\n </span>\n );\n }\n\n return class StarRating extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n rating: typeof props.rating == 'number' ? props.rating : 0,\n selection: 0\n };\n this.hoverOver = this.hoverOver.bind(this);\n this.hoverOut = this.hoverOver.bind(this, null);\n this.handleClick = this.handleClick.bind(this);\n }\n hoverOver(event) {\n let val = 0;\n if (event && event.target && event.target.getAttribute('star-id'))\n val = event.target.getAttribute('star-id');\n this.setState(state => ({ selection: val }));\n }\n handleClick(event) {\n const val = event.target.getAttribute('star-id') || this.state.rating;\n this.setState(state => ({ rating: val }));\n }\n render() {\n return (\n <div\n onMouseOut={this.hoverOut}\n onClick={this.handleClick}\n onMouseOver={this.hoverOver}\n >\n {Array.from({ length: 5 }, (v, i) => (\n <Star\n starId={i+1}\n key={`star_${i+1} `}\n marked={\n this.state.selection\n ? this.state.selection >= i+1\n : this.state.rating >= i+1\n }\n />\n ))}\n </div>\n );\n }\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",
"class"
],
"notes": []
},
{
"name": "Tabs.md",
"title": "Tab",
"text": "Renders a tabbed menu and view component.\n\nDefine `TabItem` as a middleware, pass it to the `Tab` and remove unnecessary nodes expect for `TabItem` by identifying the function's name in `props.children`.\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 `state.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\nclass Tab extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n bindIndex: props.defaultIndex\n };\n }\n changeTab(newIndex) {\n if (typeof this.props.onTabClick === \"function\")\n this.props.onTabClick(newIndex);\n this.setState({\n bindIndex: newIndex\n });\n }\n buttonClass(index) {\n return this.state.bindIndex === index ? \"focus\" : \"\";\n }\n itemStyle(index) {\n return {\n display: this.state.bindIndex === index ? \"block\" : \"none\"\n };\n }\n render() {\n const items = this.props.children.filter(\n 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={() => this.changeTab(index)}\n className={this.buttonClass(index)}\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={this.itemStyle(props.index)}\n />\n ))}\n </div>\n </div>\n );\n }\n}\nfunction TabItem(props) {\n return <div {...props} />;\n}\n```",
"```jsx\nReactDOM.render(\n <Tab 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 </Tab>,\n document.getElementById(\"root\")\n);\n\n```"
],
"expertise": 1,
"tags": [
"visual",
"children",
"class"
],
"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",
"functional"
],
"notes": []
},
{
"name": "Ticker.md",
"title": "Ticker",
"text": "Renders a ticker component.\n\n- The ticker state is initially set to zero \n- When the `Tick!` button is clicked, `timer` is incremented periodically at the given `interval`\n- When the `Reset` button is clicked, the value of the timer is set to zero and the `setInterval` is cleared\n- The `setInterval` is cleared once the desired `time` is reached\n- `time` and `interval` are the required props\n\n",
"codeBlocks": [
"```jsx\nclass Ticker extends Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.state = {ticker: 0}\n\t\tthis.interval = null\n\t}\n\n\ttick = () => {\n\t\tthis.reset()\n\t\tthis.interval = setInterval(() => {\n\t\t\tif (this.state.ticker < this.props.times) {\n\t\t\t\tthis.setState(({ ticker }) => ({ticker: ticker + 1}))\n\t\t\t}else{\n\t\t\t\tclearInterval(this.interval)\n\t\t\t}\n\t\t}, this.props.interval)\n\t}\n\n\treset = () => {\n\t\tthis.setState({ticker: 0})\n\t\tclearInterval(this.interval)\n\t}\n\n\trender() {\n\t\treturn (\n\t\t\t<div>\n\t\t\t\t<span style={{fontSize: 100}}>{this.state.ticker}</span> \n\t\t\t\t<button onClick={this.tick}>Tick!</button>\n\t\t\t\t<button onClick={this.reset}>Reset</button>\n\t\t\t</div>\n\t\t);\n\t}\n}\n```",
"```jsx\nReactDOM.render(<Ticker times={5} interval={1000} />, document.getElementById('root'));\n```"
],
"expertise": 1,
"tags": [
"visual",
"state",
"class"
],
"notes": []
},
{
"name": "Toggle.md",
"title": "Toggle",
"text": "Renders a toggle component.\n\nInitialize `state.isToggleOn` to `false`, bind the `handleClick` method to the component's context.\nUse an object, `style`, to hold the styles for individual components and their states.\nCreate a method, `handleClick`, which uses `Component.prototype.setState` to change the component's `state.toggleOn`.\nIn the `render()` method, destructure `state` and `style`, create a `<button>` that alters the component's `state` and determine the appearance of the content based on `state.isToggleOn`, applying the appropriate CSS rules from the `style` object.\n\n",
"codeBlocks": [
"```jsx\nclass Toggle extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n isToggleOn: false\n };\n this.style = {\n on: {\n backgroundColor: 'green'\n },\n off: {\n backgroundColor: 'grey'\n }\n };\n\n this.handleClick = this.handleClick.bind(this);\n }\n\n handleClick() {\n this.setState(state => ({\n isToggleOn: !state.isToggleOn\n }));\n }\n\n render() {\n const { isToggleOn } = this.state;\n const { on, off } = this.style;\n\n return (\n <button\n onClick={this.handleClick}\n style={isToggleOn ? on : off}\n >\n {isToggleOn ? 'ON' : 'OFF'}\n </button>\n );\n }\n}\n```",
"```jsx\nReactDOM.render(<Toggle />, document.getElementById('root'));\n```"
],
"expertise": 0,
"tags": [
"visual",
"state",
"class"
],
"notes": []
},
{
"name": "Tooltip.md",
"title": "Tooltip",
"text": "Renders a tooltip component.\n\nSet the `state` of the component to `show: false` initially, define an object, `style`, to hold the styles for individual components and their states.\nCreate a method, `toggleTooltip`, which uses `this.setState` to change the state's `show` property from `true` to `false` and vice versa.\nBind `showTooltip` and `hideTooltip` to the component's context with the respective values of `true` and `false`.\nIn the `render()` method, compute if the tooltip should be shown or hidden, render the content of the tooltip and bind the `onMouseEnter` and `onMouseLeave` events to `showTooltip` and `hideTooltip` respectively.\n \n",
"codeBlocks": [
"```jsx\nclass Tooltip extends React.Component {\n constructor(props) {\n super(props);\n this.state = {\n show: false\n };\n this.style = {\n tooltip: {\n position: 'relative',\n backgroundColor: \"rgba(0,0,0,0.7)\",\n color: \"white\",\n visibility: \"hidden\",\n width: \"fit-content\",\n padding: 5,\n borderRadius: 5\n },\n tooltipArrow: {\n position: 'absolute',\n top: '100%',\n left: '50%',\n borderWidth: 5,\n borderStyle: 'solid',\n borderColor: \"rgba(0,0,0,0.7) transparent transparent\",\n },\n visible: {\n visibility: \"visible\"\n },\n };\n this.showTooltip = this.toggleTooltip.bind(this, true);\n this.hideTooltip = this.toggleTooltip.bind(this, false);\n }\n\n toggleTooltip = tooltipState => {\n this.setState({\n show: tooltipState\n });\n };\n\n render() {\n const { children, text, ...rest } = this.props;\n const { show } = this.state;\n const { visible, tooltip, tooltipArrow } = this.style;\n const showTooltip = show ? visible : {};\n return (\n <div>\n <div style={{ ...tooltip, ...showTooltip }}>\n {text}\n <span style={tooltipArrow}/>\n </div>\n <div {...rest} onMouseEnter={this.showTooltip} onMouseLeave={this.hideTooltip}>\n {children}\n </div>\n </div>\n );\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",
"class"
],
"notes": []
}
]

521
package-lock.json generated Normal file
View File

@ -0,0 +1,521 @@
{
"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==",
"requires": {
"color-convert": "^1.9.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
},
"caller-callsite": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
"integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
"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=",
"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="
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.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=="
},
"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==",
"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="
},
"cosmiconfig": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz",
"integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==",
"requires": {
"import-fresh": "^2.0.0",
"is-directory": "^0.3.1",
"js-yaml": "^3.9.0",
"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==",
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"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==",
"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==",
"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="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"execa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"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==",
"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==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"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=="
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"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=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"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=="
},
"husky": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz",
"integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==",
"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=",
"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="
},
"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=",
"requires": {
"builtin-modules": "^1.0.0"
}
},
"is-ci": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
"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="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"js-yaml": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz",
"integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"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=="
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"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==",
"requires": {
"husky": "^1.0.0-rc.14"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"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==",
"requires": {
"hosted-git-info": "^2.1.4",
"is-builtin-module": "^1.0.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"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=",
"requires": {
"path-key": "^2.0.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-limit": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
"integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
"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==",
"requires": {
"p-limit": "^2.0.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=="
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"requires": {
"error-ex": "^1.3.1",
"json-parse-better-errors": "^1.0.1"
}
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
"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==",
"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==",
"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==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"read-pkg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
"integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
"requires": {
"normalize-package-data": "^2.3.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="
},
"run-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
"integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A=="
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
},
"semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w="
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"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="
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
},
"spdx-correct": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
"integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-exceptions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA=="
},
"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==",
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"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=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"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=="
},
"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==",
"requires": {
"spdx-correct": "^3.0.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==",
"requires": {
"isexe": "^2.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

29
package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "30-seconds-of-react",
"version": "1.0.0",
"description": "Curated collection of useful React snippets that you can understand in 30 seconds or less.",
"main": "index.js",
"scripts": {
"extractor": "node ./scripts/extract.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/30-seconds/30-seconds-of-react.git"
},
"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",
"devDependencies": {
"chalk": "^2.4.2",
"fs-extra": "^7.0.1",
"markdown-builder": "^0.9.0"
}
}

59
scripts/extract.js Normal file
View File

@ -0,0 +1,59 @@
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() !== "")
// const answer = getSection("#### Answer", contents)
// const goodToHear = getSection("#### Good to hear", contents, false)
// .split("\n")
// .map(v => v.replace(/[*-] /g, ""))
// .filter(v => v.trim() !== "")
// const links = getSection("##### Additional links", contents)
// .split("\n")
// .filter(v =>
// /(\/\*[\w\'\s\r\n\*]*\*\/)|(\/\/[\w\s\']*)|(\<![\-\-\s\w\>\/]*\>)/.test(
// v
// )
// )
// .map(v => v.replace(/[*-] /g, ""))
// .filter(v => v.trim() !== "" && !v.includes("tags"))
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
View 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
};

View File

@ -47,7 +47,7 @@ ReactDOM.render(
);
```
< !--tags: input,state,class -- >
<!-- tags: input,state,class -->
< !--expertise: 0 -- >
<!-- expertise: 0 -->

View File

@ -47,3 +47,7 @@ class Ticker extends Component {
```jsx
ReactDOM.render(<Ticker times={5} interval={1000} />, document.getElementById('root'));
```
<!-- tags: visual,state,class -->
<!-- expertise: 1 -->