From f9566ba43d5fefe2b2ff037ab247a84d9ba78f65 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 17 Aug 2022 20:32:41 +0000 Subject: [PATCH 01/19] Extract reusable Pill component --- site/src/components/Pill/Pill.tsx | 63 +++++++++++++ .../WorkspaceStatusBadge.tsx | 88 +++---------------- 2 files changed, 77 insertions(+), 74 deletions(-) create mode 100644 site/src/components/Pill/Pill.tsx diff --git a/site/src/components/Pill/Pill.tsx b/site/src/components/Pill/Pill.tsx new file mode 100644 index 0000000000000..f4363dfaa1ea3 --- /dev/null +++ b/site/src/components/Pill/Pill.tsx @@ -0,0 +1,63 @@ +import { makeStyles } from "@material-ui/core/styles" +import { MONOSPACE_FONT_FAMILY } from "theme/constants" +import { combineClasses } from "util/combineClasses" + +// Couldn't find a type for this so I made one. We can extend the palette if needed with module augmentation. +export type PaletteIndex = "primary" | "secondary" | "info" | "success" | "error" | "warning" + +export interface PillProps { + className?: string + icon: React.ReactNode + text: string + type?: PaletteIndex +} + +export const Pill: React.FC = ({ className, icon, text, type }) => { + const styles = useStyles({ type }) + return ( +
+
{icon}
+ {text} +
+ ) +} + +const useStyles = makeStyles((theme) => ({ + wrapper: { + fontFamily: MONOSPACE_FONT_FAMILY, + display: "inline-flex", + alignItems: "center", + borderWidth: 1, + borderStyle: "solid", + borderRadius: 99999, + fontSize: 14, + fontWeight: 500, + color: "#FFF", + height: theme.spacing(3), + paddingLeft: theme.spacing(0.75), + paddingRight: theme.spacing(1.5), + whiteSpace: "nowrap", + }, + + pillColor: { + backgroundColor: ({ type }: { type?: PaletteIndex }) => + type ? theme.palette[type].dark : theme.palette.text.secondary, + borderColor: ({ type }: { type?: PaletteIndex }) => + type ? theme.palette[type].main : theme.palette.text.secondary, + }, + + iconWrapper: { + marginRight: theme.spacing(0.5), + width: theme.spacing(2), + height: theme.spacing(2), + lineHeight: 0, + display: "flex", + alignItems: "center", + justifyContent: "center", + + "& > svg": { + width: theme.spacing(2), + height: theme.spacing(2), + }, + }, +})) diff --git a/site/src/components/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx b/site/src/components/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx index 2e27724cbc29c..9f7158d11f206 100644 --- a/site/src/components/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx +++ b/site/src/components/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx @@ -1,12 +1,10 @@ import CircularProgress from "@material-ui/core/CircularProgress" -import { makeStyles, Theme, useTheme } from "@material-ui/core/styles" import ErrorIcon from "@material-ui/icons/ErrorOutline" import StopIcon from "@material-ui/icons/PauseOutlined" import PlayIcon from "@material-ui/icons/PlayArrowOutlined" import { WorkspaceBuild } from "api/typesGenerated" +import { PaletteIndex, Pill } from "components/Pill/Pill" import React from "react" -import { MONOSPACE_FONT_FAMILY } from "theme/constants" -import { combineClasses } from "util/combineClasses" import { getWorkspaceStatus } from "util/workspace" const StatusLanguage = { @@ -28,11 +26,9 @@ const LoadingIcon: React.FC = () => { } export const getStatus = ( - theme: Theme, build: WorkspaceBuild, ): { - borderColor: string - backgroundColor: string + type?: PaletteIndex text: string icon: React.ReactNode } => { @@ -40,78 +36,66 @@ export const getStatus = ( switch (status) { case undefined: return { - borderColor: theme.palette.text.secondary, - backgroundColor: theme.palette.text.secondary, text: StatusLanguage.loading, icon: , } case "started": return { - borderColor: theme.palette.success.main, - backgroundColor: theme.palette.success.dark, + type: "success", text: StatusLanguage.started, icon: , } case "starting": return { - borderColor: theme.palette.success.main, - backgroundColor: theme.palette.success.dark, + type: "success", text: StatusLanguage.starting, icon: , } case "stopping": return { - borderColor: theme.palette.warning.main, - backgroundColor: theme.palette.warning.dark, + type: "warning", text: StatusLanguage.stopping, icon: , } case "stopped": return { - borderColor: theme.palette.warning.main, - backgroundColor: theme.palette.warning.dark, + type: "warning", text: StatusLanguage.stopped, icon: , } case "deleting": return { - borderColor: theme.palette.warning.main, - backgroundColor: theme.palette.warning.dark, + type: "warning", text: StatusLanguage.deleting, icon: , } case "deleted": return { - borderColor: theme.palette.error.main, - backgroundColor: theme.palette.error.dark, + type: "error", text: StatusLanguage.deleted, icon: , } case "canceling": return { - borderColor: theme.palette.warning.main, - backgroundColor: theme.palette.warning.dark, + type: "warning", text: StatusLanguage.canceling, icon: , } case "canceled": return { - borderColor: theme.palette.warning.main, - backgroundColor: theme.palette.warning.dark, + type: "warning", text: StatusLanguage.canceled, icon: , } case "error": return { - borderColor: theme.palette.error.main, - backgroundColor: theme.palette.error.dark, + type: "error", text: StatusLanguage.failed, icon: , } case "queued": return { - borderColor: theme.palette.info.main, - backgroundColor: theme.palette.info.dark, + type: "info", text: StatusLanguage.queued, icon: , } @@ -125,50 +109,6 @@ export type WorkspaceStatusBadgeProps = { } export const WorkspaceStatusBadge: React.FC = ({ build, className }) => { - const styles = useStyles() - const theme = useTheme() - const { text, icon, ...colorStyles } = getStatus(theme, build) - return ( -
-
{icon}
- {text} -
- ) + const { text, icon, type } = getStatus(build) + return } - -const useStyles = makeStyles((theme) => ({ - wrapper: { - fontFamily: MONOSPACE_FONT_FAMILY, - display: "inline-flex", - alignItems: "center", - borderWidth: 1, - borderStyle: "solid", - borderRadius: 99999, - fontSize: 14, - fontWeight: 500, - color: "#FFF", - height: theme.spacing(3), - paddingLeft: theme.spacing(0.75), - paddingRight: theme.spacing(1.5), - whiteSpace: "nowrap", - }, - - iconWrapper: { - marginRight: theme.spacing(0.5), - width: theme.spacing(2), - height: theme.spacing(2), - lineHeight: 0, - display: "flex", - alignItems: "center", - justifyContent: "center", - - "& > svg": { - width: theme.spacing(2), - height: theme.spacing(2), - }, - }, -})) From 7a6dd98aa3c0bc556f84a3f668da4306ca552607 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 17 Aug 2022 20:38:13 +0000 Subject: [PATCH 02/19] Make icon optional --- site/src/components/Pill/Pill.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/site/src/components/Pill/Pill.tsx b/site/src/components/Pill/Pill.tsx index f4363dfaa1ea3..9c3fac696a5a4 100644 --- a/site/src/components/Pill/Pill.tsx +++ b/site/src/components/Pill/Pill.tsx @@ -1,4 +1,5 @@ import { makeStyles } from "@material-ui/core/styles" +import React from "react" import { MONOSPACE_FONT_FAMILY } from "theme/constants" import { combineClasses } from "util/combineClasses" @@ -7,16 +8,16 @@ export type PaletteIndex = "primary" | "secondary" | "info" | "success" | "error export interface PillProps { className?: string - icon: React.ReactNode + icon?: React.ReactNode text: string type?: PaletteIndex } export const Pill: React.FC = ({ className, icon, text, type }) => { - const styles = useStyles({ type }) + const styles = useStyles({ icon, type }) return (
-
{icon}
+ {icon &&
{icon}
} {text}
) @@ -34,7 +35,8 @@ const useStyles = makeStyles((theme) => ({ fontWeight: 500, color: "#FFF", height: theme.spacing(3), - paddingLeft: theme.spacing(0.75), + paddingLeft: ({ icon }: { icon?: React.ReactNode }) => + icon ? theme.spacing(0.75) : theme.spacing(1.5), paddingRight: theme.spacing(1.5), whiteSpace: "nowrap", }, From 0b6e3367ff636da245458e1b253d52cf608785f0 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 17 Aug 2022 21:20:47 +0000 Subject: [PATCH 03/19] Get pills in place --- .../LicenseBannerView.stories.tsx | 27 ++++++++++++ .../LicenseBanner/LicenseBannerView.tsx | 43 +++++++++++++++++++ site/src/theme/palettes.ts | 2 +- 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 site/src/components/LicenseBanner/LicenseBannerView.stories.tsx create mode 100644 site/src/components/LicenseBanner/LicenseBannerView.tsx diff --git a/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx b/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx new file mode 100644 index 0000000000000..c93a9d26e5d76 --- /dev/null +++ b/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx @@ -0,0 +1,27 @@ +import { Story } from "@storybook/react" +import { LicenseBannerView, LicenseBannerViewProps } from "./LicenseBannerView" + +export default { + title: "components/LicenseBannerView", + component: LicenseBannerView, +} + +const Template: Story = (args) => + +export const OneWarning = Template.bind({}) +OneWarning.args = { + warnings: ["You have exceeded the number of seats in your license"], +} + +export const TwoWarnings = Template.bind({}) +TwoWarnings.args = { + warnings: [ + "You have exceeded the number of seats in your license", + "You are flying too close to the sun", + ], +} + +export const NoWarnings = Template.bind({}) +NoWarnings.args = { + warnings: [], +} diff --git a/site/src/components/LicenseBanner/LicenseBannerView.tsx b/site/src/components/LicenseBanner/LicenseBannerView.tsx new file mode 100644 index 0000000000000..94f4a7fe3ed45 --- /dev/null +++ b/site/src/components/LicenseBanner/LicenseBannerView.tsx @@ -0,0 +1,43 @@ +import { makeStyles, darken } from "@material-ui/core/styles" +import { Pill } from "components/Pill/Pill" + +const Language = { + licenseIssue: "License Issue", + licenseIssues: (num: number) => `${num} License Issues` +} + +export interface LicenseBannerViewProps { + warnings?: string[] +} + +export const LicenseBannerView: React.FC = ({ warnings }) => { + const styles = useStyles() + if (warnings && warnings.length) { + if (warnings.length === 1) { + return ( +
+ + {warnings[0]} +
+ ) + } else { + return ( +
+ + {warnings.map((warning, i) => ( +

{warning}

+ ))} +
+ ) + } + } else { + return null + } +} + +const useStyles = makeStyles((theme) => ({ + container: { + padding: theme.spacing(1.5), + backgroundColor: darken(theme.palette.warning.main, .2) + } +})) diff --git a/site/src/theme/palettes.ts b/site/src/theme/palettes.ts index e2e23cdf4dbeb..3ef760a1c306a 100644 --- a/site/src/theme/palettes.ts +++ b/site/src/theme/palettes.ts @@ -24,7 +24,7 @@ export const darkPalette: PaletteOptions = { }, divider: colors.gray[13], warning: { - main: colors.orange[11], + main: colors.orange[9], dark: colors.orange[15], }, success: { From dc624495154a6657cd0f7546fb60416644ef3391 Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Wed, 17 Aug 2022 22:22:56 +0000 Subject: [PATCH 04/19] Rough styling --- .../LicenseBannerView.stories.tsx | 6 +- .../LicenseBanner/LicenseBannerView.tsx | 66 ++++++++++++++++--- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx b/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx index c93a9d26e5d76..149c08f15b743 100644 --- a/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx +++ b/site/src/components/LicenseBanner/LicenseBannerView.stories.tsx @@ -10,14 +10,14 @@ const Template: Story = (args) => `${num} License Issues` + licenseIssues: (num: number) => `${num} License Issues`, + upgrade: "Contact us to upgrade your license.", + exceeded: "It looks like you've exceeded some limits of your license.", + lessDetails: "Less", + moreDetails: "More", } export interface LicenseBannerViewProps { @@ -12,21 +19,46 @@ export interface LicenseBannerViewProps { export const LicenseBannerView: React.FC = ({ warnings }) => { const styles = useStyles() + const [showDetails, setShowDetails] = useState(false) if (warnings && warnings.length) { if (warnings.length === 1) { return (
- {warnings[0]} + {warnings[0]} +   + + {Language.upgrade} +
) } else { return (
- {warnings.map((warning, i) => ( -

{warning}

- ))} + {Language.exceeded} +   + + {Language.upgrade} + +   + setShowDetails((showDetails: boolean) => !showDetails)} + className={styles.detailLink} + tabIndex={0} + > + {showDetails ? Language.lessDetails : Language.moreDetails} + + +
    + {warnings.map((warning, i) => ( +
  • + {warning} +
  • + ))} +
+
) } @@ -38,6 +70,24 @@ export const LicenseBannerView: React.FC = ({ warnings } const useStyles = makeStyles((theme) => ({ container: { padding: theme.spacing(1.5), - backgroundColor: darken(theme.palette.warning.main, .2) - } + backgroundColor: darken(theme.palette.warning.main, 0.2), + }, + text: { + marginLeft: theme.spacing(1), + }, + link: { + color: "inherit", + textDecoration: "none", + fontWeight: "bold", + }, + detailLink: { + cursor: "pointer", + color: `${lighten(theme.palette.primary.light, 0.2)}`, + }, + list: { + margin: theme.spacing(1.5), + }, + listItem: { + margin: theme.spacing(1), + }, })) From 5c208ce75fa4c19cce787d2bd1b37c94929c591f Mon Sep 17 00:00:00 2001 From: Presley Pizzo Date: Thu, 18 Aug 2022 15:51:58 +0000 Subject: [PATCH 05/19] Extract Expander component --- .../DropdownArrows/DropdownArrows.tsx | 14 ++++-- .../components/ErrorSummary/ErrorSummary.tsx | 22 +++----- site/src/components/Expander/Expander.tsx | 50 +++++++++++++++++++ .../LicenseBanner/LicenseBannerView.tsx | 41 ++++++++------- 4 files changed, 85 insertions(+), 42 deletions(-) create mode 100644 site/src/components/Expander/Expander.tsx diff --git a/site/src/components/DropdownArrows/DropdownArrows.tsx b/site/src/components/DropdownArrows/DropdownArrows.tsx index 6f64ad36d1451..642fb27456df9 100644 --- a/site/src/components/DropdownArrows/DropdownArrows.tsx +++ b/site/src/components/DropdownArrows/DropdownArrows.tsx @@ -6,7 +6,7 @@ import { FC } from "react" const useStyles = makeStyles((theme: Theme) => ({ arrowIcon: { color: fade(theme.palette.primary.contrastText, 0.7), - marginLeft: theme.spacing(1), + marginLeft: (margin: boolean) => (margin ? theme.spacing(1) : 0), width: 16, height: 16, }, @@ -15,12 +15,16 @@ const useStyles = makeStyles((theme: Theme) => ({ }, })) -export const OpenDropdown: FC = () => { - const styles = useStyles() +interface ArrowProps { + margin: boolean +} + +export const OpenDropdown: FC = ({ margin = true }) => { + const styles = useStyles(margin) return } -export const CloseDropdown: FC = () => { - const styles = useStyles() +export const CloseDropdown: FC = ({ margin = true }) => { + const styles = useStyles(margin) return } diff --git a/site/src/components/ErrorSummary/ErrorSummary.tsx b/site/src/components/ErrorSummary/ErrorSummary.tsx index f61fb9931cd3d..7af06036cd1b3 100644 --- a/site/src/components/ErrorSummary/ErrorSummary.tsx +++ b/site/src/components/ErrorSummary/ErrorSummary.tsx @@ -1,11 +1,11 @@ import Button from "@material-ui/core/Button" import Collapse from "@material-ui/core/Collapse" import IconButton from "@material-ui/core/IconButton" -import Link from "@material-ui/core/Link" import { darken, lighten, makeStyles, Theme } from "@material-ui/core/styles" import CloseIcon from "@material-ui/icons/Close" import RefreshIcon from "@material-ui/icons/Refresh" import { ApiError, getErrorDetail, getErrorMessage } from "api/errors" +import { Expander } from "components/Expander/Expander" import { Stack } from "components/Stack/Stack" import { FC, useState } from "react" @@ -36,10 +36,6 @@ export const ErrorSummary: FC = ({ const styles = useStyles({ showDetails }) - const toggleShowDetails = () => { - setShowDetails(!showDetails) - } - const closeError = () => { setOpen(false) } @@ -51,18 +47,9 @@ export const ErrorSummary: FC = ({ return ( -
+
{message} - {!!detail && ( - - {showDetails ? Language.lessDetails : Language.moreDetails} - - )} + {!!detail && }
{dismissible && ( @@ -95,6 +82,9 @@ const useStyles = makeStyles((theme) => ({ borderRadius: theme.shape.borderRadius, gap: 0, }, + flex: { + display: "flex" + }, messageBox: { justifyContent: "space-between", }, diff --git a/site/src/components/Expander/Expander.tsx b/site/src/components/Expander/Expander.tsx new file mode 100644 index 0000000000000..dd7400300e113 --- /dev/null +++ b/site/src/components/Expander/Expander.tsx @@ -0,0 +1,50 @@ +import Link from "@material-ui/core/Link" +import { lighten } from "@material-ui/core/styles" +import makeStyles from "@material-ui/core/styles/makeStyles" +import { CloseDropdown, OpenDropdown } from "components/DropdownArrows/DropdownArrows" + +const Language = { + expand: "More", + collapse: "Less", +} + +export interface CollapseButtonProps { + expanded: boolean + setExpanded: (val: boolean) => void +} + +export const Expander: React.FC = ({ expanded, setExpanded }) => { + const toggleExpanded = () => setExpanded(!expanded) + const styles = useStyles() + return ( + + {expanded ? ( + + {Language.collapse} + {" "} + + ) : ( + + {Language.expand} + + + )} + + ) +} + +const useStyles = makeStyles((theme) => ({ + expandLink: { + cursor: "pointer", + color: `${lighten(theme.palette.primary.light, 0.2)}`, + }, + text: { + display: "flex", + alignItems: "center", + }, +})) diff --git a/site/src/components/LicenseBanner/LicenseBannerView.tsx b/site/src/components/LicenseBanner/LicenseBannerView.tsx index 6683102bd6e2f..6bbb0f3f36772 100644 --- a/site/src/components/LicenseBanner/LicenseBannerView.tsx +++ b/site/src/components/LicenseBanner/LicenseBannerView.tsx @@ -1,6 +1,6 @@ import Collapse from "@material-ui/core/Collapse" -import Link from "@material-ui/core/Link" -import { darken, lighten, makeStyles } from "@material-ui/core/styles" +import { darken, makeStyles } from "@material-ui/core/styles" +import { Expander } from "components/Expander/Expander" import { Pill } from "components/Pill/Pill" import { useState } from "react" @@ -35,21 +35,17 @@ export const LicenseBannerView: React.FC = ({ warnings } } else { return (
- - {Language.exceeded} -   - - {Language.upgrade} - -   - setShowDetails((showDetails: boolean) => !showDetails)} - className={styles.detailLink} - tabIndex={0} - > - {showDetails ? Language.lessDetails : Language.moreDetails} - +
+
+ + {Language.exceeded} +   + + {Language.upgrade} + +
+ +