From 59e27e9ae9289eca30f7b1f57cca6efb369acaca Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Thu, 10 Feb 2022 04:30:13 +0000 Subject: [PATCH 1/3] Replace Create Project buttons with CLI call to action --- site/components/Button/CopyButton.tsx | 72 +++++++++++++++++++++++++++ site/components/Button/index.ts | 1 + site/components/Icons/FileCopy.tsx | 11 ++++ site/components/Icons/index.ts | 1 + site/pages/projects/index.tsx | 26 ++++------ 5 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 site/components/Button/CopyButton.tsx create mode 100644 site/components/Icons/FileCopy.tsx diff --git a/site/components/Button/CopyButton.tsx b/site/components/Button/CopyButton.tsx new file mode 100644 index 0000000000000..a8a28e9e2c8f0 --- /dev/null +++ b/site/components/Button/CopyButton.tsx @@ -0,0 +1,72 @@ +import { makeStyles } from "@material-ui/core/styles" +import Button from "@material-ui/core/Button" +import Tooltip from "@material-ui/core/Tooltip" +import React from "react" +import { FileCopy } from "../Icons" + +interface CopyButtonProps { + text: string + className?: string + onFailure: () => void + onSuccess: () => void +} + +/** + * Copy button used inside the CodeBlock component internally + */ +export const CopyButton: React.FC = ({ className = "", text, onSuccess, onFailure }) => { + const styles = useStyles() + + + const copyToClipboard = async (): Promise => { + try { + await window.navigator.clipboard.writeText(text) + onSuccess() + } catch (err) { + const wrappedErr = new Error("copyToClipboard: failed to copy text to clipboard") + if (err instanceof Error) { + wrappedErr.stack = err.stack + } + console.error(wrappedErr) + + onFailure() + } + } + + return ( + +
+ +
+
+ ) +} + +const useStyles = makeStyles((theme) => ({ + copyButtonWrapper: { + display: "flex", + marginLeft: theme.spacing(1), + }, + copyButton: { + borderRadius: 7, + background: theme.palette.codeBlock.button.main, + color: theme.palette.codeBlock.button.contrastText, + padding: theme.spacing(0.85), + minWidth: 32, + + "&:hover": { + background: theme.palette.codeBlock.button.hover, + }, + }, + fileCopyIcon: { + width: 20, + height: 20, + }, +})) + diff --git a/site/components/Button/index.ts b/site/components/Button/index.ts index 318c0c373932d..a0845e5d310f1 100644 --- a/site/components/Button/index.ts +++ b/site/components/Button/index.ts @@ -1,2 +1,3 @@ export * from "./SplitButton" export * from "./LoadingButton" +export * from "./CopyButton" diff --git a/site/components/Icons/FileCopy.tsx b/site/components/Icons/FileCopy.tsx new file mode 100644 index 0000000000000..5f06bdbfaf368 --- /dev/null +++ b/site/components/Icons/FileCopy.tsx @@ -0,0 +1,11 @@ +import SvgIcon from "@material-ui/core/SvgIcon" +import React from "react" + +export const FileCopy: typeof SvgIcon = (props) => ( + + + +) \ No newline at end of file diff --git a/site/components/Icons/index.ts b/site/components/Icons/index.ts index 17f48ebaa4a98..bb6950f2f1da9 100644 --- a/site/components/Icons/index.ts +++ b/site/components/Icons/index.ts @@ -1,4 +1,5 @@ export { CoderIcon } from "./CoderIcon" +export * from "./FileCopy" export { Logo } from "./Logo" export * from "./Logout" export { WorkspacesIcon } from "./WorkspacesIcon" diff --git a/site/pages/projects/index.tsx b/site/pages/projects/index.tsx index bf67deb52502b..eccdb914f0cf1 100644 --- a/site/pages/projects/index.tsx +++ b/site/pages/projects/index.tsx @@ -14,6 +14,7 @@ import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { Organization, Project } from "./../../api" import useSWR from "swr" +import { CodeBlock } from "../../components/CodeBlock" const ProjectsPage: React.FC = () => { const styles = useStyles() @@ -38,11 +39,6 @@ const ProjectsPage: React.FC = () => { void router.push("/projects/create") } - const action = { - text: "Create Project", - onClick: createProject, - } - // Create a dictionary of organization ID -> organization Name // Needed to properly construct links to dive into a project const orgDictionary = orgs.reduce((acc: Record, curr: Organization) => { @@ -62,16 +58,14 @@ const ProjectsPage: React.FC = () => { }, ] - const emptyState = ( - - ) + const description =
+
Run the following command to get started:
+ +
+ + const emptyState = const tableProps = { title: "All Projects", @@ -85,7 +79,7 @@ const ProjectsPage: React.FC = () => { return (
-
+
From 9b84598f23180a328110498e89c64ec361991ef1 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Fri, 11 Feb 2022 03:30:36 +0000 Subject: [PATCH 2/3] Add CodeExample component --- site/components/Button/CopyButton.tsx | 7 +--- site/components/CodeBlock/index.stories.tsx | 2 +- site/components/CodeBlock/index.tsx | 3 +- site/components/CodeExample/index.stories.tsx | 20 +++++++++++ site/components/CodeExample/index.test.tsx | 15 ++++++++ site/components/CodeExample/index.tsx | 34 +++++++++++++++++++ site/pages/projects/index.tsx | 4 +-- site/theme/palettes.ts | 23 +++++++++++++ 8 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 site/components/CodeExample/index.stories.tsx create mode 100644 site/components/CodeExample/index.test.tsx create mode 100644 site/components/CodeExample/index.tsx diff --git a/site/components/Button/CopyButton.tsx b/site/components/Button/CopyButton.tsx index a8a28e9e2c8f0..993e8976ac4db 100644 --- a/site/components/Button/CopyButton.tsx +++ b/site/components/Button/CopyButton.tsx @@ -7,29 +7,24 @@ import { FileCopy } from "../Icons" interface CopyButtonProps { text: string className?: string - onFailure: () => void - onSuccess: () => void } /** * Copy button used inside the CodeBlock component internally */ -export const CopyButton: React.FC = ({ className = "", text, onSuccess, onFailure }) => { +export const CopyButton: React.FC = ({ className = "", text }) => { const styles = useStyles() const copyToClipboard = async (): Promise => { try { await window.navigator.clipboard.writeText(text) - onSuccess() } catch (err) { const wrappedErr = new Error("copyToClipboard: failed to copy text to clipboard") if (err instanceof Error) { wrappedErr.stack = err.stack } console.error(wrappedErr) - - onFailure() } } diff --git a/site/components/CodeBlock/index.stories.tsx b/site/components/CodeBlock/index.stories.tsx index b31d450e21246..86e14ffca2856 100644 --- a/site/components/CodeBlock/index.stories.tsx +++ b/site/components/CodeBlock/index.stories.tsx @@ -12,7 +12,7 @@ export default { title: "CodeBlock", component: CodeBlock, argTypes: { - lines: { control: "object", defaultValue: sampleLines }, + lines: { control: "text", defaultValue: sampleLines }, }, } diff --git a/site/components/CodeBlock/index.tsx b/site/components/CodeBlock/index.tsx index 6b8b1afd336a1..e32468f705afc 100644 --- a/site/components/CodeBlock/index.tsx +++ b/site/components/CodeBlock/index.tsx @@ -1,5 +1,6 @@ import { makeStyles } from "@material-ui/core/styles" import React from "react" +import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" export interface CodeBlockProps { lines: string[] @@ -18,8 +19,6 @@ export const CodeBlock: React.FC = ({ lines }) => { ) } -const MONOSPACE_FONT_FAMILY = - "'Fira Code', 'Lucida Console', 'Lucida Sans Typewriter', 'Liberation Mono', 'Monaco', 'Courier New', Courier, monospace" const useStyles = makeStyles((theme) => ({ root: { diff --git a/site/components/CodeExample/index.stories.tsx b/site/components/CodeExample/index.stories.tsx new file mode 100644 index 0000000000000..d3fc86b324066 --- /dev/null +++ b/site/components/CodeExample/index.stories.tsx @@ -0,0 +1,20 @@ +import { Story } from "@storybook/react" +import React from "react" +import { CodeExample, CodeExampleProps } from "./index" + +const sampleCode = `echo "Hello, world"` + +export default { + title: "CodeExample", + component: CodeExample, + argTypes: { + code: { control: "string", defaultValue: sampleCode }, + }, +} + +const Template: Story = (args: CodeExampleProps) => + +export const Example = Template.bind({}) +Example.args = { + code: sampleCode, +} diff --git a/site/components/CodeExample/index.test.tsx b/site/components/CodeExample/index.test.tsx new file mode 100644 index 0000000000000..4a4af3af8d5e5 --- /dev/null +++ b/site/components/CodeExample/index.test.tsx @@ -0,0 +1,15 @@ +import { screen } from "@testing-library/react" +import { render } from "../../test_helpers" +import React from "react" +import { CodeExample } from "./index" + +describe("CodeExample", () => { + it("renders code", async () => { + // When + render() + + // Then + // Both lines should be rendered + await screen.findByText("echo hello") + }) +}) diff --git a/site/components/CodeExample/index.tsx b/site/components/CodeExample/index.tsx new file mode 100644 index 0000000000000..9baf0b7e349d3 --- /dev/null +++ b/site/components/CodeExample/index.tsx @@ -0,0 +1,34 @@ +import { makeStyles } from "@material-ui/core/styles" +import React from "react" +import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" + +import { CopyButton } from "./../Button" + +export interface CodeExampleProps { + code: string +} + +export const CodeExample: React.FC = ({ code }) => { + const styles = useStyles() + + return ( +
+ {code} + +
+ ) +} + +const useStyles = makeStyles((theme) => ({ + root: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-start", + background: theme.palette.background.default, + color: theme.palette.codeBlock.contrastText, + fontFamily: MONOSPACE_FONT_FAMILY, + fontSize: 13, + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + }, +})) diff --git a/site/pages/projects/index.tsx b/site/pages/projects/index.tsx index eccdb914f0cf1..c80acfc848656 100644 --- a/site/pages/projects/index.tsx +++ b/site/pages/projects/index.tsx @@ -14,7 +14,7 @@ import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { Organization, Project } from "./../../api" import useSWR from "swr" -import { CodeBlock } from "../../components/CodeBlock" +import { CodeExample } from "../../components/CodeExample" const ProjectsPage: React.FC = () => { const styles = useStyles() @@ -60,7 +60,7 @@ const ProjectsPage: React.FC = () => { const description =
Run the following command to get started:
- +
const emptyState = Date: Fri, 11 Feb 2022 03:45:04 +0000 Subject: [PATCH 3/3] Fix up --- site/.eslintrc.yaml | 2 -- site/components/Button/CopyButton.tsx | 19 ++++++------- ...ex.stories.tsx => CodeExample.stories.tsx} | 2 +- .../{index.test.tsx => CodeExample.test.tsx} | 2 +- .../{index.tsx => CodeExample.tsx} | 8 ++++-- site/components/CodeExample/index.ts | 1 + site/components/Icons/FileCopy.tsx | 2 +- site/pages/projects/index.tsx | 27 +++++++++---------- 8 files changed, 32 insertions(+), 31 deletions(-) rename site/components/CodeExample/{index.stories.tsx => CodeExample.stories.tsx} (87%) rename site/components/CodeExample/{index.test.tsx => CodeExample.test.tsx} (88%) rename site/components/CodeExample/{index.tsx => CodeExample.tsx} (81%) create mode 100644 site/components/CodeExample/index.ts diff --git a/site/.eslintrc.yaml b/site/.eslintrc.yaml index 5f8386529919a..d6c6be9eb6b95 100644 --- a/site/.eslintrc.yaml +++ b/site/.eslintrc.yaml @@ -92,8 +92,6 @@ rules: message: "Use path imports to avoid pulling in unused modules. See: https://material-ui.com/guides/minimizing-bundle-size/" - - name: "@material-ui/core/Tooltip" - message: "Use the custom Tooltip on componens/Tooltip" no-storage/no-browser-storage: error no-unused-vars: "off" "object-curly-spacing": "off" diff --git a/site/components/Button/CopyButton.tsx b/site/components/Button/CopyButton.tsx index 993e8976ac4db..b10a6088da285 100644 --- a/site/components/Button/CopyButton.tsx +++ b/site/components/Button/CopyButton.tsx @@ -1,7 +1,8 @@ import { makeStyles } from "@material-ui/core/styles" import Button from "@material-ui/core/Button" import Tooltip from "@material-ui/core/Tooltip" -import React from "react" +import Check from "@material-ui/icons/Check" +import React, { useState } from "react" import { FileCopy } from "../Icons" interface CopyButtonProps { @@ -14,11 +15,16 @@ interface CopyButtonProps { */ export const CopyButton: React.FC = ({ className = "", text }) => { const styles = useStyles() - + const [isCopied, setIsCopied] = useState(false) const copyToClipboard = async (): Promise => { try { await window.navigator.clipboard.writeText(text) + setIsCopied(true) + + window.setTimeout(() => { + setIsCopied(false) + }, 1000) } catch (err) { const wrappedErr = new Error("copyToClipboard: failed to copy text to clipboard") if (err instanceof Error) { @@ -31,12 +37,8 @@ export const CopyButton: React.FC = ({ className = "", text }) return (
-
@@ -64,4 +66,3 @@ const useStyles = makeStyles((theme) => ({ height: 20, }, })) - diff --git a/site/components/CodeExample/index.stories.tsx b/site/components/CodeExample/CodeExample.stories.tsx similarity index 87% rename from site/components/CodeExample/index.stories.tsx rename to site/components/CodeExample/CodeExample.stories.tsx index d3fc86b324066..4eafb626a83d2 100644 --- a/site/components/CodeExample/index.stories.tsx +++ b/site/components/CodeExample/CodeExample.stories.tsx @@ -1,6 +1,6 @@ import { Story } from "@storybook/react" import React from "react" -import { CodeExample, CodeExampleProps } from "./index" +import { CodeExample, CodeExampleProps } from "./CodeExample" const sampleCode = `echo "Hello, world"` diff --git a/site/components/CodeExample/index.test.tsx b/site/components/CodeExample/CodeExample.test.tsx similarity index 88% rename from site/components/CodeExample/index.test.tsx rename to site/components/CodeExample/CodeExample.test.tsx index 4a4af3af8d5e5..7618f29d38f34 100644 --- a/site/components/CodeExample/index.test.tsx +++ b/site/components/CodeExample/CodeExample.test.tsx @@ -1,7 +1,7 @@ import { screen } from "@testing-library/react" import { render } from "../../test_helpers" import React from "react" -import { CodeExample } from "./index" +import { CodeExample } from "./CodeExample" describe("CodeExample", () => { it("renders code", async () => { diff --git a/site/components/CodeExample/index.tsx b/site/components/CodeExample/CodeExample.tsx similarity index 81% rename from site/components/CodeExample/index.tsx rename to site/components/CodeExample/CodeExample.tsx index 9baf0b7e349d3..c3145af8f349b 100644 --- a/site/components/CodeExample/index.tsx +++ b/site/components/CodeExample/CodeExample.tsx @@ -2,12 +2,15 @@ import { makeStyles } from "@material-ui/core/styles" import React from "react" import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" -import { CopyButton } from "./../Button" +import { CopyButton } from "../Button" export interface CodeExampleProps { code: string } +/** + * Component to show single-line code examples, with a copy button + */ export const CodeExample: React.FC = ({ code }) => { const styles = useStyles() @@ -23,7 +26,8 @@ const useStyles = makeStyles((theme) => ({ root: { display: "flex", flexDirection: "row", - justifyContent: "flex-start", + justifyContent: "space-between", + alignItems: "center", background: theme.palette.background.default, color: theme.palette.codeBlock.contrastText, fontFamily: MONOSPACE_FONT_FAMILY, diff --git a/site/components/CodeExample/index.ts b/site/components/CodeExample/index.ts new file mode 100644 index 0000000000000..a2c38996d0f11 --- /dev/null +++ b/site/components/CodeExample/index.ts @@ -0,0 +1 @@ +export * from "./CodeExample" diff --git a/site/components/Icons/FileCopy.tsx b/site/components/Icons/FileCopy.tsx index 5f06bdbfaf368..d8f33f101ef5f 100644 --- a/site/components/Icons/FileCopy.tsx +++ b/site/components/Icons/FileCopy.tsx @@ -8,4 +8,4 @@ export const FileCopy: typeof SvgIcon = (props) => ( fill="currentColor" /> -) \ No newline at end of file +) diff --git a/site/pages/projects/index.tsx b/site/pages/projects/index.tsx index c80acfc848656..105216e069d39 100644 --- a/site/pages/projects/index.tsx +++ b/site/pages/projects/index.tsx @@ -1,7 +1,6 @@ import React from "react" import { makeStyles } from "@material-ui/core/styles" import Paper from "@material-ui/core/Paper" -import { useRouter } from "next/router" import Link from "next/link" import { EmptyState } from "../../components" import { ErrorSummary } from "../../components/ErrorSummary" @@ -14,11 +13,10 @@ import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" import { Organization, Project } from "./../../api" import useSWR from "swr" -import { CodeExample } from "../../components/CodeExample" +import { CodeExample } from "../../components/CodeExample/CodeExample" const ProjectsPage: React.FC = () => { const styles = useStyles() - const router = useRouter() const { me, signOut } = useUser(true) const { data: projects, error } = useSWR("/api/v2/projects") const { data: orgs, error: orgsError } = useSWR("/api/v2/users/me/organizations") @@ -35,10 +33,6 @@ const ProjectsPage: React.FC = () => { return } - const createProject = () => { - void router.push("/projects/create") - } - // Create a dictionary of organization ID -> organization Name // Needed to properly construct links to dive into a project const orgDictionary = orgs.reduce((acc: Record, curr: Organization) => { @@ -58,14 +52,14 @@ const ProjectsPage: React.FC = () => { }, ] - const description =
-
Run the following command to get started:
- -
+ const description = ( +
+
Run the following command to get started:
+ +
+ ) - const emptyState = + const emptyState = const tableProps = { title: "All Projects", @@ -88,11 +82,14 @@ const ProjectsPage: React.FC = () => { ) } -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ root: { display: "flex", flexDirection: "column", }, + descriptionLabel: { + marginBottom: theme.spacing(1), + }, })) export default ProjectsPage