From ccd3a34766b0865d2d7e31de2c822acb417828a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20O=E2=80=99Connor?= Date: Wed, 11 Jan 2023 11:18:48 -0500 Subject: [PATCH] Next.js PR 4: Implement the component overview page in Next.js (#873) --- .eslintrc.js | 8 + next/components/alert/alert.module.scss | 6 + next/components/alert/alert.tsx | 50 +++++ next/components/mdx/mdx.tsx | 21 +- .../overview/overview.jsx | 197 ++++++++++++++++++ .../overview/overview.module.scss | 28 +++ next/package.json | 4 + next/pages/components/overview.tsx | 167 +++++++++++++++ yarn.lock | 18 ++ 9 files changed, 495 insertions(+), 4 deletions(-) create mode 100644 next/components/alert/alert.module.scss create mode 100644 next/components/alert/alert.tsx create mode 100644 next/components/thumbprint-components/overview/overview.jsx create mode 100644 next/components/thumbprint-components/overview/overview.module.scss create mode 100644 next/pages/components/overview.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 9ea19f880..1c4742d75 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -132,5 +132,13 @@ module.exports = { ], }, }, + { + files: ['next/pages/**/*.tsx'], + rules: { + // We can use Next.js's `InferGetStaticPropsType` rather than specifying the return + // type manually. + '@typescript-eslint/explicit-function-return-type': 'off', + }, + }, ], }; diff --git a/next/components/alert/alert.module.scss b/next/components/alert/alert.module.scss new file mode 100644 index 000000000..d0f81908a --- /dev/null +++ b/next/components/alert/alert.module.scss @@ -0,0 +1,6 @@ +@import '~@thumbtack/thumbprint-tokens/dist/scss/_index'; + +.children a { + color: $tp-color__black-300; + border-bottom: 1px solid $tp-color__gray; +} diff --git a/next/components/alert/alert.tsx b/next/components/alert/alert.tsx new file mode 100644 index 000000000..c1cd77a54 --- /dev/null +++ b/next/components/alert/alert.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Text } from '@thumbtack/thumbprint-react'; +import { + NotificationAlertsInfoFilledSmall, + NotificationAlertsWarningFilledSmall, +} from '@thumbtack/thumbprint-icons'; +import styles from './alert.module.scss'; + +const alertStyles = { + boxShadow: '0 2px 4px rgba(0,0,0,.1)', + maxWidth: '580px', +}; + +const iconStyles = 'db relative -top-2'; + +interface PropTypes { + title: React.ReactNode; + children: React.ReactNode; + type: 'warning' | 'note'; +} + +export default function Alert({ type, title, children }: PropTypes): JSX.Element { + return ( +
+
+
+ {type === 'note' && ( + + )} + {type === 'warning' && ( + + )} +
+ + {title} + +
+ + {children} + +
+ ); +} diff --git a/next/components/mdx/mdx.tsx b/next/components/mdx/mdx.tsx index 07924fbd1..87116afcd 100644 --- a/next/components/mdx/mdx.tsx +++ b/next/components/mdx/mdx.tsx @@ -255,25 +255,38 @@ export function getStaticProps(): { props: { layoutProps: LayoutProps } } { }; } -interface MdxPropTypes { +interface ContentPageProps { children: React.ReactNode; title: string; description?: string; layoutProps: LayoutProps; } -export default function MDX({ +export const ContentPage = ({ children, title, description, layoutProps, -}: MdxPropTypes): JSX.Element { +}: ContentPageProps): JSX.Element => { return ( - {children} + {children} ); +}; + +export default function MDX({ + children, + title, + description, + layoutProps, +}: ContentPageProps): JSX.Element { + return ( + + {children} + + ); } diff --git a/next/components/thumbprint-components/overview/overview.jsx b/next/components/thumbprint-components/overview/overview.jsx new file mode 100644 index 000000000..22b0669cc --- /dev/null +++ b/next/components/thumbprint-components/overview/overview.jsx @@ -0,0 +1,197 @@ +/* eslint-disable import/prefer-default-export */ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { Text } from '@thumbtack/thumbprint-react'; +import { tpColorGray300 } from '@thumbtack/thumbprint-tokens'; +import styles from './overview.module.scss'; + +const documentationStatuses = ['Unknown', 'To-do', 'In progress', 'Done', "Done / Won't do"]; +const documentationStatusPropTypes = PropTypes.oneOf(documentationStatuses); + +const developmentStatuses = [ + 'Unknown', + 'To-do', + 'In progress', + 'Done', + 'Done / Uses OS component', + "Done / Won't build", + 'Done / Deprecated', +]; +const developmentStatusPropTypes = PropTypes.oneOf(developmentStatuses); + +const designStatuses = ['Unknown', 'To-do', 'In progress', 'Done', "Done / Won't do"]; +const designStatusPropTypes = PropTypes.oneOf(designStatuses); + +export const Dot = ({ status }) => ( +
+); + +Dot.propTypes = { + status: PropTypes.oneOf([...designStatuses, ...developmentStatuses, ...documentationStatuses]) + .isRequired, +}; + +export const ComponentRow = ({ name, react, scss, ios, android }) => ( + <> + + + {name} + + + React + + + + + + + + + + + + + + SCSS + + + + + + + + + + + + + + iOS + + + + + + + + + + + + + + Android + + + + + + + + + + + + +); + +ComponentRow.propTypes = { + name: PropTypes.string.isRequired, + react: PropTypes.shape({ + design: designStatusPropTypes.isRequired, + development: developmentStatusPropTypes.isRequired, + documentation: documentationStatusPropTypes.isRequired, + }).isRequired, + scss: PropTypes.shape({ + design: designStatusPropTypes.isRequired, + development: developmentStatusPropTypes.isRequired, + documentation: documentationStatusPropTypes.isRequired, + }).isRequired, + ios: PropTypes.shape({ + design: designStatusPropTypes.isRequired, + development: developmentStatusPropTypes.isRequired, + documentation: documentationStatusPropTypes.isRequired, + }).isRequired, + android: PropTypes.shape({ + design: designStatusPropTypes.isRequired, + development: developmentStatusPropTypes.isRequired, + documentation: documentationStatusPropTypes.isRequired, + }).isRequired, +}; + +export const ComponentTable = ({ children }) => ( + + + + {/* + The `boxShadow` is used instead of `border` because of: + https://stackoverflow.com/q/41882616/316602 + */} + + + + + + + + {children} +
+ Component + + Platforms + + Design + + Development + + Documentation +
+); + +ComponentTable.propTypes = { + children: PropTypes.node.isRequired, +}; diff --git a/next/components/thumbprint-components/overview/overview.module.scss b/next/components/thumbprint-components/overview/overview.module.scss new file mode 100644 index 000000000..63fa33028 --- /dev/null +++ b/next/components/thumbprint-components/overview/overview.module.scss @@ -0,0 +1,28 @@ +@import '~@thumbtack/thumbprint-tokens/dist/scss/_index'; + +.dot { + width: 12px; + height: 12px; + border-radius: 50%; + display: inline-block; +} + +.dotDone { + background: $tp-color__green; +} + +.dotInProgress { + border: 2px solid $tp-color__yellow; +} + +.dotToDo { + border: 2px solid $tp-color__blue; +} + +.dotNotApplicable { + background: $tp-color__gray; +} + +.dotDeprecated { + background: $tp-color__red-300; +} diff --git a/next/package.json b/next/package.json index 660999be5..728cb41e8 100644 --- a/next/package.json +++ b/next/package.json @@ -18,6 +18,7 @@ "@types/react-dom": "18.0.10", "clickable-box": "^1.1.10", "gonzales-pe": "^4.3.0", + "lodash-es": "^4.17.21", "mousetrap": "^1.6.5", "next": "13.1.1", "prism-react-renderer": "0.1.7", @@ -32,5 +33,8 @@ }, "installConfig": { "hoistingLimits": "workspaces" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.6" } } diff --git a/next/pages/components/overview.tsx b/next/pages/components/overview.tsx new file mode 100644 index 000000000..a81badd4a --- /dev/null +++ b/next/pages/components/overview.tsx @@ -0,0 +1,167 @@ +import React from 'react'; +import type { InferGetStaticPropsType } from 'next'; +import { groupBy, keyBy } from 'lodash-es'; +import { Text } from '@thumbtack/thumbprint-react'; +import getLayoutProps from '../../utils/get-layout-props'; +import Alert from '../../components/alert/alert'; +import { ContentPage, InlineCode } from '../../components/mdx/mdx'; +import { + ComponentRow, + ComponentTable, + Dot, +} from '../../components/thumbprint-components/overview/overview'; + +interface Implementation { + browserLink: string; + createdAt: string; + href: string; + id: string; + index: number; + name: string; + type: string; + updatedAt: string; + values: { + Component: string; + Platform: string; + 'Ship date': string; + Source: string; + Design: string; + Documentation: string; + 'Start date': string; + 'Row ID': string; + 'JIRA Ticket': string; + 'Name used in implementation': string; + Developer: string; + 'Start (in days)': string; + 'Development status': string; + 'Design status': string; + 'Documentation status': string; + }; +} + +export default function Components({ + implementations, + layoutProps, +}: InferGetStaticPropsType): JSX.Element { + return ( + + {implementations.length === 0 && ( + + You’ll need to add CODA_API_TOKEN environment variable + to a www/.env file in order to see the component + statuses while developing locally. Read the{' '} + + CONTRIBUTING.md + {' '} + file for help. + + )} + + + + + {implementations.map(component => { + const platforms = keyBy(component, 'values.Platform'); + + return ( + + ); + })} + + + ); +} + +export const getStaticProps = async () => { + const listRowsRes = await fetch( + // https://coda.io/developers/apis/v1#operation/listRows + `https://coda.io/apis/v1/docs/bXyUQb2tJW/tables/Implementations/rows?useColumnNames=true`, + { + headers: { + Authorization: `Bearer ${process.env.CODA_API_TOKEN}`, + }, + }, + ); + + const data = listRowsRes.ok ? await listRowsRes.json() : null; + const implementations: Implementation[] = data ? data.items : []; + + const groupedImplementations = groupBy(implementations, implementation => { + return implementation.values.Component; + }); + + const groupedAndSortedImplementations = Object.keys(groupedImplementations) + .sort() + .map(key => { + return groupedImplementations[key]; + }); + + return { + props: { + layoutProps: getLayoutProps(), + implementations: groupedAndSortedImplementations, + }, + }; +}; diff --git a/yarn.lock b/yarn.lock index 7a5ea3260..5f6090340 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6648,6 +6648,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash-es@npm:^4.17.6": + version: 4.17.6 + resolution: "@types/lodash-es@npm:4.17.6" + dependencies: + "@types/lodash": "*" + checksum: 9bd239dd525086e278821949ce12fbdd4f100a060fed9323fc7ad5661113e1641f28a7ebab617230ed3474680d8f4de705c1928b48252bb684be6ec9eed715db + languageName: node + linkType: hard + "@types/lodash.sample@npm:^4.2.6": version: 4.2.6 resolution: "@types/lodash.sample@npm:4.2.6" @@ -21455,6 +21464,13 @@ is-whitespace@latest: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 + languageName: node + linkType: hard + "lodash-webpack-plugin@npm:^0.11.5": version: 0.11.5 resolution: "lodash-webpack-plugin@npm:0.11.5" @@ -23753,11 +23769,13 @@ is-whitespace@latest: "@next/mdx": ^13.1.1 "@thumbtack/thumbprint-font-face": ^1.1.0 "@thumbtack/thumbprint-react": ^14.15.0 + "@types/lodash-es": ^4.17.6 "@types/node": 18.11.18 "@types/react": 18.0.26 "@types/react-dom": 18.0.10 clickable-box: ^1.1.10 gonzales-pe: ^4.3.0 + lodash-es: ^4.17.21 mousetrap: ^1.6.5 next: 13.1.1 prism-react-renderer: 0.1.7