diff --git a/src/components/blog/Post.js b/src/components/blog/Post.js new file mode 100644 index 00000000..e979d679 --- /dev/null +++ b/src/components/blog/Post.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import parse from 'html-react-parser'; + +/** + * Displays a blog post page + * @param {object} props + * { + * title: + * content: + * modified: { + const { title, content, modified, featured_image } = page; + + return ( + <> +

+ {title} +

+

Edited: {modified}

+ featured_img +
{parse(content)}
+ + ); +}; + + +Post.propTypes = { + page: PropTypes.shape({ + title: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + modified: PropTypes.string, + featured_image: PropTypes.string, + }) +} + +export default Post; diff --git a/src/components/blog/PostList.js b/src/components/blog/PostList.js new file mode 100644 index 00000000..d641d2ce --- /dev/null +++ b/src/components/blog/PostList.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import parse from 'html-react-parser'; + +/** + * Displays a list of blog posts with the title and a short excerp from the content. + * @param {object} props + * { + * posts: { + * title: + * excerpt: + * } + * } + * @returns + */ +const PostList = ({ posts }) => { + return ( + <> +

+ {posts.length} posts found +

+ {posts.map((post, index) => ( +
+ + {parse(post.title)} + +

{parse(post.excerpt)}

+
+ ))} + + ); +}; + +PostList.propTypes = { + posts: PropTypes.object.isRequired +} + +export default PostList; diff --git a/src/components/dataset/DataExplorer.js b/src/components/dataset/DataExplorer.js new file mode 100644 index 00000000..5ce7db18 --- /dev/null +++ b/src/components/dataset/DataExplorer.js @@ -0,0 +1,19 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +/** + * Opens a frictionless resource in data explorer. Data explorer gives you + * an interface to interact with a resource. That means you can do things like + * data filtering, sorting, e.t.c + * @param {object} resource A frictionless Data resource + * @returns React component + */ +const DataExplorer = ({ resource }) => { + // TODO: Add data explorer code + return <>{JSON.stringify(resource)}; +}; + +DataExplorer.propTypes = { + resource: PropTypes.object.isRequired +} +export default DataExplorer; diff --git a/src/components/page/KeyInfo.js b/src/components/dataset/KeyInfo.js similarity index 96% rename from src/components/page/KeyInfo.js rename to src/components/dataset/KeyInfo.js index a464a99d..07139469 100644 --- a/src/components/page/KeyInfo.js +++ b/src/components/dataset/KeyInfo.js @@ -1,5 +1,6 @@ import React from 'react'; import filesize from 'filesize' +import PropTypes from 'prop-types'; /** * KeyInfo component receives two arguments. @@ -91,4 +92,8 @@ const KeyInfo = ({ descriptor, resources }) => { ) } +KeyInfo.propTypes = { + descriptor: PropTypes.object.isRequired, + resources: PropTypes.array.isRequired +} export default KeyInfo \ No newline at end of file diff --git a/src/components/dataset/Org.js b/src/components/dataset/Org.js new file mode 100644 index 00000000..c5b5c414 --- /dev/null +++ b/src/components/dataset/Org.js @@ -0,0 +1,45 @@ +import Link from 'next/link'; +import React from 'react' +import PropTypes from 'prop-types'; + +/** + * Displays information about an organization in a dataset page + * @param {Object} props object describing the dataset organization. + * organization: { + * image_url: The image url of the organization + * name: The name of the organization + * title: The title of the organization + * } + * @returns + */ +const Org = ({ organization }) => { + return ( + <> + {organization ? ( + <> + org_img + + + {organization.title || organization.name} + + + + ) : ( + '' + )} + + ); +}; + +Org.propTypes = { + organization: PropTypes.object.isRequired +} + +export default Org; diff --git a/src/components/page/Readme.js b/src/components/dataset/Readme.js similarity index 84% rename from src/components/page/Readme.js rename to src/components/dataset/Readme.js index e3573467..14ae9dbc 100644 --- a/src/components/page/Readme.js +++ b/src/components/dataset/Readme.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; /** * ReadMe component displays the markdown description of a datapackage @@ -18,4 +19,8 @@ const ReadMe = ({ readmeHtml }) => { ) } +ReadMe.propTypes = { + readmeHtml: PropTypes.string.isRequired +} + export default ReadMe \ No newline at end of file diff --git a/src/components/page/ResourceInfo.js b/src/components/dataset/ResourceInfo.js similarity index 95% rename from src/components/page/ResourceInfo.js rename to src/components/dataset/ResourceInfo.js index 94df28df..e1c9f583 100644 --- a/src/components/page/ResourceInfo.js +++ b/src/components/dataset/ResourceInfo.js @@ -1,5 +1,6 @@ import React from 'react'; import filesize from 'filesize' +import PropTypes from 'prop-types'; /** * ResourceInfo component displays all resources in a data package @@ -59,4 +60,8 @@ const ResourcesInfo = ({ resources }) => { ) } + +ResourcesInfo.propTypes = { + resources: PropTypes.array.isRequired +} export default ResourcesInfo \ No newline at end of file diff --git a/src/components/misc/CustomLink.js b/src/components/misc/CustomLink.js new file mode 100644 index 00000000..686dfdfd --- /dev/null +++ b/src/components/misc/CustomLink.js @@ -0,0 +1,27 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +/** + * Creates a custom link with title + * @param {object} props + * { + * url: The url of the custom link + * title: The title for the custom link + * } + * @returns React Component + */ +const CustomLink = ({ url, title }) => ( + + {title} + +); + +CustomLink.propTypes = { + url: PropTypes.string.isRequired, + title: PropTypes.string.isRequired +} + +export default CustomLink; diff --git a/src/components/misc/Error.js b/src/components/misc/Error.js new file mode 100644 index 00000000..39eed32b --- /dev/null +++ b/src/components/misc/Error.js @@ -0,0 +1,32 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +/** + * Error message component with consistent portal style + * @param {object} props + * { + * message: The error message to display + * } + * @returns + */ +const ErrorMessage = ({ message }) => { + return ( + + ); +}; + +ErrorMessage.propTypes = { + message: PropTypes.string.isRequired +} + +export default ErrorMessage; diff --git a/src/components/search/Form.js b/src/components/search/Form.js new file mode 100644 index 00000000..61b4aeda --- /dev/null +++ b/src/components/search/Form.js @@ -0,0 +1,62 @@ +import React from 'react' +import PropTypes from 'prop-types'; + +/** + * Search component form that can be customized with change and submit handlers + * @param {object} props + * { + * handleChange: A form input change event handler. This function is executed when the + * search input or order by input changes. + * handleSubmit: A form submit event handler. This function is executed when the + * search form is submitted. + * } + * @returns + */ +const Form = ({ handleChange, handleSubmit }) => { + + return ( +
+
+ + +
+
+ + +
+
+ ); +}; + +Form.propTypes = { + handleChange: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired +} + +export default Form; diff --git a/src/components/search/Item.js b/src/components/search/Item.js new file mode 100644 index 00000000..1d47183b --- /dev/null +++ b/src/components/search/Item.js @@ -0,0 +1,55 @@ +import React from 'react' +import Link from 'next/link'; +import PropTypes from 'prop-types'; + +/** + * Single item from a search result showing info about a dataset. + * @param {object} props data package with the following format: + * { + * organization: {name: , title: }, + * title: + * name: + * description: + * notes: + * } + * @returns React Component + */ +const Item = ({ dataset }) => { + return ( + + ); +}; + +Item.propTypes = { + dataset: PropTypes.object.isRequired +} + +export default Item; diff --git a/src/components/search/Total.js b/src/components/search/Total.js new file mode 100644 index 00000000..6bf83e17 --- /dev/null +++ b/src/components/search/Total.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +/** + * Displays the total search result + * @param {object} props + * { + * count: The total number of search results + * } + * @returns React Component + */ +const Total = ({ count }) => { + + return ( +

+ {count} results found +

+ ); +}; + +Total.propTypes = { + count: PropTypes.number.isRequired +} + +export default Total; diff --git a/src/components/ui/Home/Nav.js b/src/components/ui/Nav.js similarity index 55% rename from src/components/ui/Home/Nav.js rename to src/components/ui/Nav.js index cde83bcf..21577d82 100644 --- a/src/components/ui/Home/Nav.js +++ b/src/components/ui/Nav.js @@ -1,6 +1,17 @@ +import React from 'react' import Link from 'next/link'; -import React, { useState } from 'react'; +import { useState } from 'react'; +import PropTypes from 'prop-types'; +/** + * Displays a navigation bar with logo and menu links + * @param {Object} props object with the following properties: + * { + * logo: The relative url to the logo image + * navMenu: An array of objects with menu items. E.g : [{ title: 'Blog', path: '/blog' },{ title: 'Search', path: '/search' }] + * } + * @returns React Component + */ const Nav = ({ logo, navMenu }) => { const [open, setOpen] = useState(false); const handleClick = (event) => { @@ -30,41 +41,19 @@ const Nav = ({ logo, navMenu }) => {
{navMenu.map((menu, index) => { - + return ( {menu.title} - + ) })} - - - Blog - - - - - Search - - - - Docs - - - GitHub -
); }; +Nav.propTypes = { + logo: PropTypes.string.isRequired, + navMenu: PropTypes.string +} export default Nav; diff --git a/src/components/ui/Home/Recent.js b/src/components/ui/Recent.js similarity index 67% rename from src/components/ui/Home/Recent.js rename to src/components/ui/Recent.js index c1c0b436..665d0201 100644 --- a/src/components/ui/Home/Recent.js +++ b/src/components/ui/Recent.js @@ -1,10 +1,20 @@ import React from 'react'; import Link from 'next/link'; +import PropTypes from 'prop-types'; -const Recent = (datasets) => { - if (!datasets) { - return <> - } +/** + * Displays a list of recent datasets + * @param {object} props + * datasets = { + * organization: {name: , title: }, + * title: + * name: + * description: + * notes: + * } + * @returns React Component + */ +const Recent = ({datasets}) => { return (
@@ -34,4 +44,8 @@ const Recent = (datasets) => { ); }; +Recent.propTypes = { + datasets: PropTypes.object.isRequired +} + export default Recent; diff --git a/src/components/ui/index.js b/src/components/ui/index.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/views/Chart.js b/src/components/views/Chart.js index 188b8e70..687edb8c 100644 --- a/src/components/views/Chart.js +++ b/src/components/views/Chart.js @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; import createPlotlyComponent from "react-plotly.js/factory"; let Plot; -const PlotlyChart = (props) => { +const PlotlyChart = ({spec}) => { const [plotCreated, setPlotCreated] = useState(0) //0: false, 1: true useEffect(() => { @@ -18,7 +19,7 @@ const PlotlyChart = (props) => { return (
- { ) } +PlotlyChart.propTypes = { + spec: PropTypes.object.isRequired +} export { PlotlyChart } \ No newline at end of file diff --git a/src/components/views/Table.js b/src/components/views/Table.js index 2de9afc1..b426473b 100644 --- a/src/components/views/Table.js +++ b/src/components/views/Table.js @@ -1,24 +1,13 @@ import React from 'react'; import { DataGrid } from '@material-ui/data-grid'; +import PropTypes from 'prop-types'; /** - * Displays a table from a Frictionless dataset - * @param schema: Frictionless Table Schema - * @param data: an array of data objects e.g. [ {a: 1, b: 2}, {a: 5, b: 7} ] + * Displays dataset in tabular form using data grid + * @param columns: An array of column names with properties: e.g [{field: "col1", headerName: "col1"}, {field: "col2", headerName: "col2"}] + * @param data: an array of data objects e.g. [ {col1: 1, col2: 2}, {col1: 5, col2: 7} ] */ -const Table = ({ schema, data }) => { - const columns = schema.fields.map((field) => ( - { - field: field.title || field.name, - headerName: field.name, - width: 300 - } - )) - data = data.map((item, index)=>{ - item.id = index //Datagrid requires every row to have an ID - return item - }) - +const Table = ({ columns, data }) => { return (
@@ -26,4 +15,9 @@ const Table = ({ schema, data }) => { ); } +Table.propTypes = { + columns: PropTypes.array.isRequired, + data: PropTypes.array.isRequired +} + export default Table diff --git a/src/index.js b/src/index.js index 7b9f794a..99f31e39 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,38 @@ -//Components +//View components import Table from './components/views/Table' import { PlotlyChart } from './components/views/Chart' -import KeyInfo from './components/page/KeyInfo' -import ResourceInfo from './components/page/ResourceInfo' -import ReadMe from './components/page/Readme' -import Nav from './components/ui/Home/Nav' -import Recent from './components/ui/Home/Recent' -export { Table, PlotlyChart, KeyInfo, ResourceInfo, ReadMe, Nav, Recent } \ No newline at end of file +//Dataset components +import KeyInfo from './components/dataset/KeyInfo' +import ResourceInfo from './components/dataset/ResourceInfo' +import ReadMe from './components/dataset/Readme' +import DataExplorer from './components/dataset/DataExplorer' +import Org from './components/dataset/Org' + +//Blog components +import Post from './components/blog/Post' +import PostList from './components/blog/PostList' + +//Misc components +import Error from './components/misc/Error' +import CustomLink from './components/misc/CustomLink' + +//UI components +import Nav from './components/ui/Nav' +import Recent from './components/ui/Recent' + +export { + Table, + PlotlyChart, + KeyInfo, + ResourceInfo, + ReadMe, + Nav, + Recent, + DataExplorer, + Post, + PostList, + Org, + Error, + CustomLink +} \ No newline at end of file